From 8b6a10ead45bcd915f5da223de2b3fd3c30fc7b5 Mon Sep 17 00:00:00 2001
From: Evgeny Fadeev
Date: Sat, 12 Jun 2010 22:34:13 -0400
Subject: removed some old cruft
---
.gitignore | 1 +
askbot.iml | 10 -
cache/README.TXT | 1 -
django_authopenid/README | 5 +
dos2unix.sh | 12 -
fbconnect/__init__.py | 0
fbconnect/fb.py | 96 -
fbconnect/forms.py | 8 -
fbconnect/models.py | 6 -
fbconnect/pjson.py | 313 ----
fbconnect/tests.py | 23 -
fbconnect/urls.py | 21 -
fbconnect/views.py | 112 --
forum/badges/__init__.py | 10 -
forum/badges/base.py | 11 -
forum/bin/dos2unix.sh | 12 +
forum/bin/rmpyc | 1 +
forum/conf/skin_counter_settings.py | 2 +-
forum/const/__init__.py | 4 +-
forum/deps/README | 2 +
forum/deps/__init__.py | 0
forum/deps/grapefruit.py | 1973 +++++++++++++++++++++
forum/management/__init__.py | 3 -
forum/models/__init__.py | 9 +-
forum/modules.py | 78 -
forum/skins/default/media/js/com.cnprog.i18n.js | 450 ++---
forum/skins/default/media/style/style.css | 18 +-
forum/skins/default/templates/question_list.html | 2 +-
forum/skins/default/templates/user_responses.html | 4 +-
forum/sql_scripts/update_2010_01_23.sql | 9 -
forum/templatetags/extra_filters.py | 2 +-
forum/templatetags/extra_tags.py | 10 +-
forum/tests.py | 2 +-
forum/utils/colors.py | 2 +-
forum/views/users.py | 4 +-
forum_modules/__init__.py | 0
forum_modules/authentication/README | 3 -
forum_modules/authentication/auth.py | 144 --
forum_modules/books/__init__.py | 3 -
forum_modules/books/models.py | 63 -
forum_modules/books/urls.py | 10 -
forum_modules/books/views.py | 142 --
forum_modules/grapefruit.py | 1973 ---------------------
forum_modules/pgfulltext/DISABLED | 0
forum_modules/pgfulltext/__init__.py | 9 -
forum_modules/pgfulltext/handlers.py | 11 -
forum_modules/pgfulltext/management.py | 30 -
forum_modules/pgfulltext/pg_fts_install.sql | 38 -
forum_modules/robotstxt/DISABLED | 0
forum_modules/robotstxt/__init__.py | 0
forum_modules/robotstxt/templates/robots.txt | 2 -
forum_modules/robotstxt/urls.py | 6 -
forum_modules/sphinxfulltext/DISABLED | 0
forum_modules/sphinxfulltext/__init__.py | 0
forum_modules/sphinxfulltext/dependencies.py | 2 -
forum_modules/sphinxfulltext/handlers.py | 4 -
forum_modules/sphinxfulltext/models.py | 11 -
forum_modules/sphinxfulltext/settings.py | 5 -
livesettings/README | 4 +
rmpyc | 1 -
run | 1 -
run-tests | 1 -
62 files changed, 2251 insertions(+), 3428 deletions(-)
delete mode 100755 askbot.iml
delete mode 100755 cache/README.TXT
create mode 100644 django_authopenid/README
delete mode 100644 dos2unix.sh
delete mode 100644 fbconnect/__init__.py
delete mode 100644 fbconnect/fb.py
delete mode 100644 fbconnect/forms.py
delete mode 100644 fbconnect/models.py
delete mode 100644 fbconnect/pjson.py
delete mode 100644 fbconnect/tests.py
delete mode 100644 fbconnect/urls.py
delete mode 100644 fbconnect/views.py
delete mode 100644 forum/badges/__init__.py
delete mode 100644 forum/badges/base.py
create mode 100644 forum/bin/dos2unix.sh
create mode 100755 forum/bin/rmpyc
create mode 100644 forum/deps/README
create mode 100644 forum/deps/__init__.py
create mode 100644 forum/deps/grapefruit.py
delete mode 100644 forum/modules.py
delete mode 100755 forum/sql_scripts/update_2010_01_23.sql
delete mode 100644 forum_modules/__init__.py
delete mode 100644 forum_modules/authentication/README
delete mode 100644 forum_modules/authentication/auth.py
delete mode 100644 forum_modules/books/__init__.py
delete mode 100644 forum_modules/books/models.py
delete mode 100644 forum_modules/books/urls.py
delete mode 100644 forum_modules/books/views.py
delete mode 100644 forum_modules/grapefruit.py
delete mode 100644 forum_modules/pgfulltext/DISABLED
delete mode 100644 forum_modules/pgfulltext/__init__.py
delete mode 100644 forum_modules/pgfulltext/handlers.py
delete mode 100644 forum_modules/pgfulltext/management.py
delete mode 100644 forum_modules/pgfulltext/pg_fts_install.sql
delete mode 100755 forum_modules/robotstxt/DISABLED
delete mode 100644 forum_modules/robotstxt/__init__.py
delete mode 100755 forum_modules/robotstxt/templates/robots.txt
delete mode 100644 forum_modules/robotstxt/urls.py
delete mode 100644 forum_modules/sphinxfulltext/DISABLED
delete mode 100644 forum_modules/sphinxfulltext/__init__.py
delete mode 100644 forum_modules/sphinxfulltext/dependencies.py
delete mode 100644 forum_modules/sphinxfulltext/handlers.py
delete mode 100644 forum_modules/sphinxfulltext/models.py
delete mode 100644 forum_modules/sphinxfulltext/settings.py
create mode 100644 livesettings/README
delete mode 100755 rmpyc
delete mode 100755 run
delete mode 100755 run-tests
diff --git a/.gitignore b/.gitignore
index 42ce2e42..2f2899a3 100755
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
*.swp
*.log
cache/??
+run
*.wsgi
nbproject
settings_local.py
diff --git a/askbot.iml b/askbot.iml
deleted file mode 100755
index 4e760f0a..00000000
--- a/askbot.iml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/cache/README.TXT b/cache/README.TXT
deleted file mode 100755
index 54247a82..00000000
--- a/cache/README.TXT
+++ /dev/null
@@ -1 +0,0 @@
-this file is just a placeholder so the empty directory is not ignored by version control
\ No newline at end of file
diff --git a/django_authopenid/README b/django_authopenid/README
new file mode 100644
index 00000000..67c33d60
--- /dev/null
+++ b/django_authopenid/README
@@ -0,0 +1,5 @@
+this is a forked version of django-authopenid module
+specifically for askbot forum project.
+
+most likely it is not useful for anything else and
+in fact will be phased out in askbot as well
diff --git a/dos2unix.sh b/dos2unix.sh
deleted file mode 100644
index 2864426a..00000000
--- a/dos2unix.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#please take care not to dos2unix anything in your .git directory
-#because that will probably break your repo
-dos2unix `find . -name '*.py'`
-dos2unix `find . -name '*.po'`
-dos2unix `find . -name '*.js'`
-dos2unix `find . -name '*.css'`
-dos2unix `find . -name '*.txt'`
-dos2unix `find ./sphinx -type f`
-dos2unix `find ./cron -type f`
-dos2unix settings_local.py.dist
-dos2unix README
-dos2unix INSTALL
diff --git a/fbconnect/__init__.py b/fbconnect/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/fbconnect/fb.py b/fbconnect/fb.py
deleted file mode 100644
index 8d41c3a2..00000000
--- a/fbconnect/fb.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from forum.conf import settings as forum_settings
-from time import time
-from datetime import datetime
-from urllib import urlopen, urlencode
-
-try:
- from json import load as load_json
-except:
- from pjson import fread as load_json
-
-from models import FBAssociation
-import hashlib
-import logging
-
-REST_SERVER = 'http://api.facebook.com/restserver.php'
-
-def generate_sig(values):
- keys = []
-
- for key in sorted(values.keys()):
- keys.append(key)
-
- signature = ''.join(['%s=%s' % (key, values[key]) for key in keys]) + forum_settings.FB_SECRET
- return hashlib.md5(signature).hexdigest()
-
-def check_cookies_signature(cookies):
- API_KEY = forum_settings.FB_API_KEY
-
- values = {}
-
- for key in cookies.keys():
- if (key.startswith(API_KEY + '_')):
- values[key.replace(API_KEY + '_', '')] = cookies[key]
-
- return generate_sig(values) == cookies[API_KEY]
-
-def get_user_data(cookies):
- request_data = {
- 'method': 'Users.getInfo',
- 'api_key': forum_settings.FB_API_KEY,
- 'call_id': time(),
- 'v': '1.0',
- 'uids': cookies[forum_settings.FB_API_KEY + '_user'],
- 'fields': 'name,first_name,last_name',
- 'format': 'json',
- }
-
- request_data['sig'] = generate_sig(request_data)
- fb_response = urlopen(REST_SERVER, urlencode(request_data))
- #print(fb_response)
- return load_json(fb_response)[0]
-
-
-def delete_cookies(response):
- API_KEY = forum_settings.FB_API_KEY
-
- response.delete_cookie(API_KEY + '_user')
- response.delete_cookie(API_KEY + '_session_key')
- response.delete_cookie(API_KEY + '_expires')
- response.delete_cookie(API_KEY + '_ss')
- response.delete_cookie(API_KEY)
- response.delete_cookie('fbsetting_' + API_KEY)
-
-def check_session_expiry(cookies):
- return datetime.fromtimestamp(float(cookies[forum_settings.FB_API_KEY+'_expires'])) > datetime.now()
-
-STATES = {
- 'FIRSTTIMER': 1,
- 'SESSIONEXPIRED': 2,
- 'RETURNINGUSER': 3,
- 'INVALIDSTATE': 4,
-}
-
-def get_user_state(request):
- API_KEY = forum_settings.FB_API_KEY
- logging.debug('')
-
- if API_KEY in request.COOKIES:
- logging.debug('FB API key is in request cookies')
- if check_cookies_signature(request.COOKIES):
- logging.debug('FB cookie signature is fine')
- if check_session_expiry(request.COOKIES):
- logging.debug('FB session is not expired')
- try:
- uassoc = FBAssociation.objects.get(fbuid=request.COOKIES[API_KEY + '_user'])
- logging.debug('found existing FB user association')
- return (STATES['RETURNINGUSER'], uassoc.user)
- except:
- logging.debug('dont have FB association for this user')
- return (STATES['FIRSTTIMER'], get_user_data(request.COOKIES))
- else:
- logging.debug('FB session expired')
- return (STATES['SESSIONEXPIRED'], None)
- logging.debug('FB state is INVALID')
-
- return (STATES['INVALIDSTATE'], None)
diff --git a/fbconnect/forms.py b/fbconnect/forms.py
deleted file mode 100644
index 94f86816..00000000
--- a/fbconnect/forms.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django_authopenid.forms import NextUrlField, UserNameField, UserEmailField
-
-from django import forms
-
-class FBConnectRegisterForm(forms.Form):
- next = NextUrlField()
- username = UserNameField()
- email = UserEmailField()
diff --git a/fbconnect/models.py b/fbconnect/models.py
deleted file mode 100644
index 2172217d..00000000
--- a/fbconnect/models.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.db import models
-from django.contrib.auth.models import User
-
-class FBAssociation(models.Model):
- user = models.ForeignKey(User)
- fbuid = models.CharField(max_length=12, unique=True)
diff --git a/fbconnect/pjson.py b/fbconnect/pjson.py
deleted file mode 100644
index 273b684e..00000000
--- a/fbconnect/pjson.py
+++ /dev/null
@@ -1,313 +0,0 @@
-import string
-import types
-
-## json.py implements a JSON (http://json.org) reader and writer.
-## Copyright (C) 2005 Patrick D. Logan
-## Contact mailto:patrickdlogan@stardecisions.com
-##
-## 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
-
-
-class _StringGenerator(object):
- def __init__(self, string):
- self.string = string
- self.index = -1
- def peek(self):
- i = self.index + 1
- if i < len(self.string):
- return self.string[i]
- else:
- return None
- def next(self):
- self.index += 1
- if self.index < len(self.string):
- return self.string[self.index]
- else:
- raise StopIteration
- def all(self):
- return self.string
-
-class WriteException(Exception):
- pass
-
-class ReadException(Exception):
- pass
-
-class JsonReader(object):
- hex_digits = {'A': 10,'B': 11,'C': 12,'D': 13,'E': 14,'F':15}
- escapes = {'t':'\t','n':'\n','f':'\f','r':'\r','b':'\b'}
-
- def read(self, s):
- self._generator = _StringGenerator(s)
- result = self._read()
- return result
-
- def _read(self):
- self._eatWhitespace()
- peek = self._peek()
- if peek is None:
- raise ReadException, "Nothing to read: '%s'" % self._generator.all()
- if peek == '{':
- return self._readObject()
- elif peek == '[':
- return self._readArray()
- elif peek == '"':
- return self._readString()
- elif peek == '-' or peek.isdigit():
- return self._readNumber()
- elif peek == 't':
- return self._readTrue()
- elif peek == 'f':
- return self._readFalse()
- elif peek == 'n':
- return self._readNull()
- elif peek == '/':
- self._readComment()
- return self._read()
- else:
- raise ReadException, "Input is not valid JSON: '%s'" % self._generator.all()
-
- def _readTrue(self):
- self._assertNext('t', "true")
- self._assertNext('r', "true")
- self._assertNext('u', "true")
- self._assertNext('e', "true")
- return True
-
- def _readFalse(self):
- self._assertNext('f', "false")
- self._assertNext('a', "false")
- self._assertNext('l', "false")
- self._assertNext('s', "false")
- self._assertNext('e', "false")
- return False
-
- def _readNull(self):
- self._assertNext('n', "null")
- self._assertNext('u', "null")
- self._assertNext('l', "null")
- self._assertNext('l', "null")
- return None
-
- def _assertNext(self, ch, target):
- if self._next() != ch:
- raise ReadException, "Trying to read %s: '%s'" % (target, self._generator.all())
-
- def _readNumber(self):
- isfloat = False
- result = self._next()
- peek = self._peek()
- while peek is not None and (peek.isdigit() or peek == "."):
- isfloat = isfloat or peek == "."
- result = result + self._next()
- peek = self._peek()
- try:
- if isfloat:
- return float(result)
- else:
- return int(result)
- except ValueError:
- raise ReadException, "Not a valid JSON number: '%s'" % result
-
- def _readString(self):
- result = ""
- assert self._next() == '"'
- try:
- while self._peek() != '"':
- ch = self._next()
- if ch == "\\":
- ch = self._next()
- if ch in 'brnft':
- ch = self.escapes[ch]
- elif ch == "u":
- ch4096 = self._next()
- ch256 = self._next()
- ch16 = self._next()
- ch1 = self._next()
- n = 4096 * self._hexDigitToInt(ch4096)
- n += 256 * self._hexDigitToInt(ch256)
- n += 16 * self._hexDigitToInt(ch16)
- n += self._hexDigitToInt(ch1)
- ch = unichr(n)
- elif ch not in '"/\\':
- raise ReadException, "Not a valid escaped JSON character: '%s' in %s" % (ch, self._generator.all())
- result = result + ch
- except StopIteration:
- raise ReadException, "Not a valid JSON string: '%s'" % self._generator.all()
- assert self._next() == '"'
- return result
-
- def _hexDigitToInt(self, ch):
- try:
- result = self.hex_digits[ch.upper()]
- except KeyError:
- try:
- result = int(ch)
- except ValueError:
- raise ReadException, "The character %s is not a hex digit." % ch
- return result
-
- def _readComment(self):
- assert self._next() == "/"
- second = self._next()
- if second == "/":
- self._readDoubleSolidusComment()
- elif second == '*':
- self._readCStyleComment()
- else:
- raise ReadException, "Not a valid JSON comment: %s" % self._generator.all()
-
- def _readCStyleComment(self):
- try:
- done = False
- while not done:
- ch = self._next()
- done = (ch == "*" and self._peek() == "/")
- if not done and ch == "/" and self._peek() == "*":
- raise ReadException, "Not a valid JSON comment: %s, '/*' cannot be embedded in the comment." % self._generator.all()
- self._next()
- except StopIteration:
- raise ReadException, "Not a valid JSON comment: %s, expected */" % self._generator.all()
-
- def _readDoubleSolidusComment(self):
- try:
- ch = self._next()
- while ch != "\r" and ch != "\n":
- ch = self._next()
- except StopIteration:
- pass
-
- def _readArray(self):
- result = []
- assert self._next() == '['
- done = self._peek() == ']'
- while not done:
- item = self._read()
- result.append(item)
- self._eatWhitespace()
- done = self._peek() == ']'
- if not done:
- ch = self._next()
- if ch != ",":
- raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
- assert ']' == self._next()
- return result
-
- def _readObject(self):
- result = {}
- assert self._next() == '{'
- done = self._peek() == '}'
- while not done:
- key = self._read()
- if type(key) is not types.StringType:
- raise ReadException, "Not a valid JSON object key (should be a string): %s" % key
- self._eatWhitespace()
- ch = self._next()
- if ch != ":":
- raise ReadException, "Not a valid JSON object: '%s' due to: '%s'" % (self._generator.all(), ch)
- self._eatWhitespace()
- val = self._read()
- result[key] = val
- self._eatWhitespace()
- done = self._peek() == '}'
- if not done:
- ch = self._next()
- if ch != ",":
- raise ReadException, "Not a valid JSON array: '%s' due to: '%s'" % (self._generator.all(), ch)
- assert self._next() == "}"
- return result
-
- def _eatWhitespace(self):
- p = self._peek()
- while p is not None and p in string.whitespace or p == '/':
- if p == '/':
- self._readComment()
- else:
- self._next()
- p = self._peek()
-
- def _peek(self):
- return self._generator.peek()
-
- def _next(self):
- return self._generator.next()
-
-class JsonWriter(object):
-
- def _append(self, s):
- self._results.append(s)
-
- def write(self, obj, escaped_forward_slash=False):
- self._escaped_forward_slash = escaped_forward_slash
- self._results = []
- self._write(obj)
- return "".join(self._results)
-
- def _write(self, obj):
- ty = type(obj)
- if ty is types.DictType:
- n = len(obj)
- self._append("{")
- for k, v in obj.items():
- self._write(k)
- self._append(":")
- self._write(v)
- n = n - 1
- if n > 0:
- self._append(",")
- self._append("}")
- elif ty is types.ListType or ty is types.TupleType:
- n = len(obj)
- self._append("[")
- for item in obj:
- self._write(item)
- n = n - 1
- if n > 0:
- self._append(",")
- self._append("]")
- elif ty is types.StringType or ty is types.UnicodeType:
- self._append('"')
- obj = obj.replace('\\', r'\\')
- if self._escaped_forward_slash:
- obj = obj.replace('/', r'\/')
- obj = obj.replace('"', r'\"')
- obj = obj.replace('\b', r'\b')
- obj = obj.replace('\f', r'\f')
- obj = obj.replace('\n', r'\n')
- obj = obj.replace('\r', r'\r')
- obj = obj.replace('\t', r'\t')
- self._append(obj)
- self._append('"')
- elif ty is types.IntType or ty is types.LongType:
- self._append(str(obj))
- elif ty is types.FloatType:
- self._append("%f" % obj)
- elif obj is True:
- self._append("true")
- elif obj is False:
- self._append("false")
- elif obj is None:
- self._append("null")
- else:
- raise WriteException, "Cannot write in JSON: %s" % repr(obj)
-
-def write(obj, escaped_forward_slash=False):
- return JsonWriter().write(obj, escaped_forward_slash)
-
-def read(s):
- return JsonReader().read(s)
-
-def fread(f):
- return read(f.read())
diff --git a/fbconnect/tests.py b/fbconnect/tests.py
deleted file mode 100644
index 2247054b..00000000
--- a/fbconnect/tests.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""
-This file demonstrates two different styles of tests (one doctest and one
-unittest). These will both pass when you run "manage.py test".
-
-Replace these with more appropriate tests for your application.
-"""
-
-from django.test import TestCase
-
-class SimpleTest(TestCase):
- def test_basic_addition(self):
- """
- Tests that 1 + 1 always equals 2.
- """
- self.failUnlessEqual(1 + 1, 2)
-
-__test__ = {"doctest": """
-Another way to test that 1 + 1 is equal to 2.
-
->>> 1 + 1 == 2
-True
-"""}
-
diff --git a/fbconnect/urls.py b/fbconnect/urls.py
deleted file mode 100644
index 81b0cb0f..00000000
--- a/fbconnect/urls.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from django.conf.urls.defaults import *
-from django.utils.translation import ugettext as _
-from django.views.generic.simple import direct_to_template
-from views import signin, register
-
-urlpatterns = patterns('',
- url(
- r'^xd_receiver$',
- direct_to_template,
- {'template': 'fbconnect/xd_receiver.html',},
- name='xd_receiver'
- ),
-
- url(r'^%s$' % _('signin/'), signin, name="fb_signin"),
- url(r'^%s%s$' % (_('signin/'), _('newquestion/')), signin, {'newquestion': True}, name="fb_signin_new_question"),
- url(r'^%s%s$' % (_('signin/'), _('newanswer/')), signin, {'newanswer': True}, name="fb_signin_new_answer"),
-
- url(r'^%s$' % _('register/'), register, name="fb_user_register"),
- url(r'^%s%s$' % (_('register/'), _('newquestion/')), register, {'newquestion': True}, name="fb_user_register_new_question"),
- url(r'^%s%s$' % (_('register/'), _('newanswer/')), register, {'newanswer': True}, name="fb_user_register_new_answer"),
-)
diff --git a/fbconnect/views.py b/fbconnect/views.py
deleted file mode 100644
index 91ea757a..00000000
--- a/fbconnect/views.py
+++ /dev/null
@@ -1,112 +0,0 @@
-from django.shortcuts import render_to_response as render
-from django.template import RequestContext
-from django.http import HttpResponseRedirect
-from django.utils.safestring import mark_safe
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from django.contrib.auth import login, logout
-from models import FBAssociation
-from forum.forms import SimpleEmailSubscribeForm
-from django.conf import settings
-
-import fb
-import forms
-
-import logging
-
-def signin(request, newquestion = False, newanswer = False):
- logging.debug('')
- state, context = fb.get_user_state(request)
-
- if state == fb.STATES['FIRSTTIMER']:
- logging.debug('FB state = FIRSTTIMER')
- if newquestion:
- register_url = 'fb_user_register_new_question'
- elif newanswer:
- register_url = 'fb_user_register_new_answer'
- else:
- register_url = 'fb_user_register'
- return HttpResponseRedirect(reverse(register_url))
- elif state == fb.STATES['RETURNINGUSER']:
- logging.debug('FB state = RETURNINGUSER')
- return login_and_forward(request, context, newquestion, newanswer)
- elif state == fb.STATES['SESSIONEXPIRED']:
- logging.debug('FB state = SESSIONEXPIRED')
- response = logout(request, next_page=reverse('index'))
- fb.delete_cookies(response)
- return response
-
- return HttpResponseRedirect(reverse('index'))
-
-def register(request, newquestion = False, newanswer = False):
- logging.debug('')
- state, context = fb.get_user_state(request)
-
- if state == fb.STATES['FIRSTTIMER']:
- logging.debug('FB FIRSTTIMER - try to register locally')
- logging.debug('request method is %s' % request.method)
- if request.method == 'POST' and 'bnewaccount' in request.POST:
- form1 = forms.FBConnectRegisterForm(request.POST)
- email_feeds_form = SimpleEmailSubscribeForm(request.POST)
-
- if (form1.is_valid() and email_feeds_form.is_valid()):
- tmp_pwd = User.objects.make_random_password()
- user_ = User.objects.create_user(form1.cleaned_data['username'],
- form1.cleaned_data['email'], tmp_pwd)
-
- user_.set_unusable_password()
- logging.debug('created new internal user %s' % form1.cleaned_data['username'])
-
- uassoc = FBAssociation(user=user_, fbuid=context['uid'])
- uassoc.save()
- logging.debug('created new user association')
-
- email_feeds_form.save(user_)
-
- return login_and_forward(request, user_, newquestion, newanswer)
- else:
- logging.debug('form user input is invalid')
- else:
- form1 = forms.FBConnectRegisterForm(initial={
- 'next': '/',
- 'username': context['name'],
- 'email': '',
- })
- email_feeds_form = SimpleEmailSubscribeForm()
-
- return render('authopenid/complete.html', {
- 'form1': form1,
- 'email_feeds_form': email_feeds_form,
- 'provider':mark_safe('facebook'),
- 'login_type':'facebook',
- 'gravatar_faq_url':reverse('faq') + '#gravatar',
- }, context_instance=RequestContext(request))
- else:
- logging.debug('not a FIRSTTIMER --> redirect to index view')
- return HttpResponseRedirect(reverse('index'))
-
-def login_and_forward(request, user, newquestion = False, newanswer = False):
- old_session = request.session.session_key
- user.backend = "django.contrib.auth.backends.ModelBackend"
- logging.debug('attached auth.backends.ModelBackend to this FB user')
- login(request, user)
- logging.debug('user logged in!')
-
- from forum.models import signals#todo: move to authentication app
- signals.user_logged_in.send(user=user,session_key=old_session,sender=None)
- logging.debug('user_logged_in signal sent')
-
- if (newquestion):
- from forum.models import Question
- question = Question.objects.filter(author=user).order_by('-added_at')[0]
- logging.debug('redirecting to newly posted question')
- return HttpResponseRedirect(question.get_absolute_url())
-
- if (newanswer):
- from forum.models import Answer
- answer = Answer.objects.filter(author=user).order_by('-added_at')[0]
- logging.debug('redirecting to newly posted answer')
- return HttpResponseRedirect(answer.get_absolute_url())
-
- logging.debug('redirecting to front page')
- return HttpResponseRedirect('/')
diff --git a/forum/badges/__init__.py b/forum/badges/__init__.py
deleted file mode 100644
index 8d7cd097..00000000
--- a/forum/badges/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import re
-
-from forum.badges.base import BadgeImplementation
-from forum.modules import get_modules_script_classes
-
-ALL_BADGES = dict([
- (re.sub('BadgeImpl', '', name).lower(), cls) for name, cls
- in get_modules_script_classes('badges', BadgeImplementation).items()
- if not re.search('AbstractBadgeImpl$', name)
- ])
\ No newline at end of file
diff --git a/forum/badges/base.py b/forum/badges/base.py
deleted file mode 100644
index 03ef3565..00000000
--- a/forum/badges/base.py
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-class BadgeImplementation(object):
- name = ""
- description = ""
-
- def install(self):
- pass
-
- def process_job(self):
- raise NotImplementedError
\ No newline at end of file
diff --git a/forum/bin/dos2unix.sh b/forum/bin/dos2unix.sh
new file mode 100644
index 00000000..2864426a
--- /dev/null
+++ b/forum/bin/dos2unix.sh
@@ -0,0 +1,12 @@
+#please take care not to dos2unix anything in your .git directory
+#because that will probably break your repo
+dos2unix `find . -name '*.py'`
+dos2unix `find . -name '*.po'`
+dos2unix `find . -name '*.js'`
+dos2unix `find . -name '*.css'`
+dos2unix `find . -name '*.txt'`
+dos2unix `find ./sphinx -type f`
+dos2unix `find ./cron -type f`
+dos2unix settings_local.py.dist
+dos2unix README
+dos2unix INSTALL
diff --git a/forum/bin/rmpyc b/forum/bin/rmpyc
new file mode 100755
index 00000000..014575f6
--- /dev/null
+++ b/forum/bin/rmpyc
@@ -0,0 +1 @@
+rm `find . -name '*.pyc'`
diff --git a/forum/conf/skin_counter_settings.py b/forum/conf/skin_counter_settings.py
index 51c7e332..5a9d177f 100644
--- a/forum/conf/skin_counter_settings.py
+++ b/forum/conf/skin_counter_settings.py
@@ -4,7 +4,7 @@ Skin settings to color view, vote and answer counters
from forum.conf.settings_wrapper import settings
from livesettings import ConfigurationGroup, IntegerValue, StringValue
from django.utils.translation import ugettext as _
-from forum_modules.grapefruit import Color
+from forum.deps.grapefruit import Color
SKIN_COUNTER_SETTINGS = ConfigurationGroup(
'SKIN_COUNTER_SETTINGS',
diff --git a/forum/const/__init__.py b/forum/const/__init__.py
index b1c9de7a..d77ba6b7 100644
--- a/forum/const/__init__.py
+++ b/forum/const/__init__.py
@@ -104,8 +104,8 @@ TYPE_ACTIVITY_MENTION = 19
#todo: rename this to TYPE_ACTIVITY_CHOICES
TYPE_ACTIVITY = (
- (TYPE_ACTIVITY_ASK_QUESTION, _('question')),
- (TYPE_ACTIVITY_ANSWER, _('answer')),
+ (TYPE_ACTIVITY_ASK_QUESTION, _('asked a question')),
+ (TYPE_ACTIVITY_ANSWER, _('answered a question')),
(TYPE_ACTIVITY_COMMENT_QUESTION, _('commented question')),
(TYPE_ACTIVITY_COMMENT_ANSWER, _('commented answer')),
(TYPE_ACTIVITY_UPDATE_QUESTION, _('edited question')),
diff --git a/forum/deps/README b/forum/deps/README
new file mode 100644
index 00000000..2dfee4dd
--- /dev/null
+++ b/forum/deps/README
@@ -0,0 +1,2 @@
+any python modules that are not accessible
+through easy_install, but are necessary for the askbot forum
diff --git a/forum/deps/__init__.py b/forum/deps/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/forum/deps/grapefruit.py b/forum/deps/grapefruit.py
new file mode 100644
index 00000000..ca684745
--- /dev/null
+++ b/forum/deps/grapefruit.py
@@ -0,0 +1,1973 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-#
+
+# Copyright (c) 2008, Xavier Basty
+#
+# 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.
+
+'''GrapeFruit - Color manipulation in Python'''
+
+# $Id: grapefruit.py 31 2008-06-15 10:48:06Z xbasty $
+__author__ = 'Xavier Basty '
+__version__ = '0.1a3'
+
+
+# The default white reference, use 2° Standard Observer, D65 (daylight)
+_DEFAULT_WREF = (0.95043, 1.00000, 1.08890)
+
+_oneThird = 1.0 / 3
+_srgbGammaCorrInv = 0.03928 / 12.92
+_sixteenHundredsixteenth = 16.0 / 116
+
+_RybWheel = (
+ 0, 26, 52,
+ 83, 120, 130,
+ 141, 151, 162,
+ 177, 190, 204,
+ 218, 232, 246,
+ 261, 275, 288,
+ 303, 317, 330,
+ 338, 345, 352,
+ 360)
+
+_RgbWheel = (
+ 0, 8, 17,
+ 26, 34, 41,
+ 48, 54, 60,
+ 81, 103, 123,
+ 138, 155, 171,
+ 187, 204, 219,
+ 234, 251, 267,
+ 282, 298, 329,
+ 360)
+
+class Color:
+ '''Hold a color value.
+
+ Example usage:
+
+ To create an instance of the grapefruit.Color from RGB values:
+
+ >>> import grapefruit
+ >>> r, g, b = 1, 0.5, 0
+ >>> col = grapefruit.Color.NewFromRgb(r, g, b)
+
+ To get the values of the color in another colorspace:
+
+ >>> h, s, v = col.hsv
+ >>> l, a, b = col.lab
+
+ To get the complementary of a color:
+
+ >>> compl = col.ComplementaryColor()
+ >>> print compl.hsl
+ (210.0, 1.0, 0.5)
+
+ To directly convert RGB values to their HSL equivalent:
+
+ >>> h, s, l = Color.RgbToHsl(r, g, b)
+
+ '''
+
+ WHITE_REFERENCE = {
+ 'std_A' : (1.09847, 1.00000, 0.35582),
+ 'std_B' : (0.99093, 1.00000, 0.85313),
+ 'std_C' : (0.98071, 1.00000, 1.18225),
+ 'std_D50' : (0.96421, 1.00000, 0.82519),
+ 'std_D55' : (0.95680, 1.00000, 0.92148),
+ 'std_D65' : (0.95043, 1.00000, 1.08890),
+ 'std_D75' : (0.94972, 1.00000, 1.22639),
+ 'std_E' : (1.00000, 1.00000, 1.00000),
+ 'std_F1' : (0.92834, 1.00000, 1.03665),
+ 'std_F2' : (0.99145, 1.00000, 0.67316),
+ 'std_F3' : (1.03753, 1.00000, 0.49861),
+ 'std_F4' : (1.09147, 1.00000, 0.38813),
+ 'std_F5' : (0.90872, 1.00000, 0.98723),
+ 'std_F6' : (0.97309, 1.00000, 0.60191),
+ 'std_F7' : (0.95017, 1.00000, 1.08630),
+ 'std_F8' : (0.96413, 1.00000, 0.82333),
+ 'std_F9' : (1.00365, 1.00000, 0.67868),
+ 'std_F10' : (0.96174, 1.00000, 0.81712),
+ 'std_F11' : (1.00899, 1.00000, 0.64262),
+ 'std_F12' : (1.08046, 1.00000, 0.39228),
+ 'sup_A' : (1.11142, 1.00000, 0.35200),
+ 'sup_B' : (0.99178, 1.00000, 0.84349),
+ 'sup_C' : (0.97286, 1.00000, 1.16145),
+ 'sup_D50' : (0.96721, 1.00000, 0.81428),
+ 'sup_D55' : (0.95797, 1.00000, 0.90925),
+ 'sup_D65' : (0.94810, 1.00000, 1.07305),
+ 'sup_D75' : (0.94417, 1.00000, 1.20643),
+ 'sup_E' : (1.00000, 1.00000, 1.00000),
+ 'sup_F1' : (0.94791, 1.00000, 1.03191),
+ 'sup_F2' : (1.03245, 1.00000, 0.68990),
+ 'sup_F3' : (1.08968, 1.00000, 0.51965),
+ 'sup_F4' : (1.14961, 1.00000, 0.40963),
+ 'sup_F5' : (0.93369, 1.00000, 0.98636),
+ 'sup_F6' : (1.02148, 1.00000, 0.62074),
+ 'sup_F7' : (0.95780, 1.00000, 1.07618),
+ 'sup_F8' : (0.97115, 1.00000, 0.81135),
+ 'sup_F9' : (1.02116, 1.00000, 0.67826),
+ 'sup_F10' : (0.99001, 1.00000, 0.83134),
+ 'sup_F11' : (1.03820, 1.00000, 0.65555),
+ 'sup_F12' : (1.11428, 1.00000, 0.40353)}
+
+ NAMED_COLOR = {
+ 'aliceblue': '#f0f8ff',
+ 'antiquewhite': '#faebd7',
+ 'aqua': '#00ffff',
+ 'aquamarine': '#7fffd4',
+ 'azure': '#f0ffff',
+ 'beige': '#f5f5dc',
+ 'bisque': '#ffe4c4',
+ 'black': '#000000',
+ 'blanchedalmond': '#ffebcd',
+ 'blue': '#0000ff',
+ 'blueviolet': '#8a2be2',
+ 'brown': '#a52a2a',
+ 'burlywood': '#deb887',
+ 'cadetblue': '#5f9ea0',
+ 'chartreuse': '#7fff00',
+ 'chocolate': '#d2691e',
+ 'coral': '#ff7f50',
+ 'cornflowerblue': '#6495ed',
+ 'cornsilk': '#fff8dc',
+ 'crimson': '#dc143c',
+ 'cyan': '#00ffff',
+ 'darkblue': '#00008b',
+ 'darkcyan': '#008b8b',
+ 'darkgoldenrod': '#b8860b',
+ 'darkgray': '#a9a9a9',
+ 'darkgrey': '#a9a9a9',
+ 'darkgreen': '#006400',
+ 'darkkhaki': '#bdb76b',
+ 'darkmagenta': '#8b008b',
+ 'darkolivegreen': '#556b2f',
+ 'darkorange': '#ff8c00',
+ 'darkorchid': '#9932cc',
+ 'darkred': '#8b0000',
+ 'darksalmon': '#e9967a',
+ 'darkseagreen': '#8fbc8f',
+ 'darkslateblue': '#483d8b',
+ 'darkslategray': '#2f4f4f',
+ 'darkslategrey': '#2f4f4f',
+ 'darkturquoise': '#00ced1',
+ 'darkviolet': '#9400d3',
+ 'deeppink': '#ff1493',
+ 'deepskyblue': '#00bfff',
+ 'dimgray': '#696969',
+ 'dimgrey': '#696969',
+ 'dodgerblue': '#1e90ff',
+ 'firebrick': '#b22222',
+ 'floralwhite': '#fffaf0',
+ 'forestgreen': '#228b22',
+ 'fuchsia': '#ff00ff',
+ 'gainsboro': '#dcdcdc',
+ 'ghostwhite': '#f8f8ff',
+ 'gold': '#ffd700',
+ 'goldenrod': '#daa520',
+ 'gray': '#808080',
+ 'grey': '#808080',
+ 'green': '#008000',
+ 'greenyellow': '#adff2f',
+ 'honeydew': '#f0fff0',
+ 'hotpink': '#ff69b4',
+ 'indianred': '#cd5c5c',
+ 'indigo': '#4b0082',
+ 'ivory': '#fffff0',
+ 'khaki': '#f0e68c',
+ 'lavender': '#e6e6fa',
+ 'lavenderblush': '#fff0f5',
+ 'lawngreen': '#7cfc00',
+ 'lemonchiffon': '#fffacd',
+ 'lightblue': '#add8e6',
+ 'lightcoral': '#f08080',
+ 'lightcyan': '#e0ffff',
+ 'lightgoldenrodyellow': '#fafad2',
+ 'lightgreen': '#90ee90',
+ 'lightgray': '#d3d3d3',
+ 'lightgrey': '#d3d3d3',
+ 'lightpink': '#ffb6c1',
+ 'lightsalmon': '#ffa07a',
+ 'lightseagreen': '#20b2aa',
+ 'lightskyblue': '#87cefa',
+ 'lightslategray': '#778899',
+ 'lightslategrey': '#778899',
+ 'lightsteelblue': '#b0c4de',
+ 'lightyellow': '#ffffe0',
+ 'lime': '#00ff00',
+ 'limegreen': '#32cd32',
+ 'linen': '#faf0e6',
+ 'magenta': '#ff00ff',
+ 'maroon': '#800000',
+ 'mediumaquamarine': '#66cdaa',
+ 'mediumblue': '#0000cd',
+ 'mediumorchid': '#ba55d3',
+ 'mediumpurple': '#9370db',
+ 'mediumseagreen': '#3cb371',
+ 'mediumslateblue': '#7b68ee',
+ 'mediumspringgreen': '#00fa9a',
+ 'mediumturquoise': '#48d1cc',
+ 'mediumvioletred': '#c71585',
+ 'midnightblue': '#191970',
+ 'mintcream': '#f5fffa',
+ 'mistyrose': '#ffe4e1',
+ 'moccasin': '#ffe4b5',
+ 'navajowhite': '#ffdead',
+ 'navy': '#000080',
+ 'oldlace': '#fdf5e6',
+ 'olive': '#808000',
+ 'olivedrab': '#6b8e23',
+ 'orange': '#ffa500',
+ 'orangered': '#ff4500',
+ 'orchid': '#da70d6',
+ 'palegoldenrod': '#eee8aa',
+ 'palegreen': '#98fb98',
+ 'paleturquoise': '#afeeee',
+ 'palevioletred': '#db7093',
+ 'papayawhip': '#ffefd5',
+ 'peachpuff': '#ffdab9',
+ 'peru': '#cd853f',
+ 'pink': '#ffc0cb',
+ 'plum': '#dda0dd',
+ 'powderblue': '#b0e0e6',
+ 'purple': '#800080',
+ 'red': '#ff0000',
+ 'rosybrown': '#bc8f8f',
+ 'royalblue': '#4169e1',
+ 'saddlebrown': '#8b4513',
+ 'salmon': '#fa8072',
+ 'sandybrown': '#f4a460',
+ 'seagreen': '#2e8b57',
+ 'seashell': '#fff5ee',
+ 'sienna': '#a0522d',
+ 'silver': '#c0c0c0',
+ 'skyblue': '#87ceeb',
+ 'slateblue': '#6a5acd',
+ 'slategray': '#708090',
+ 'slategrey': '#708090',
+ 'snow': '#fffafa',
+ 'springgreen': '#00ff7f',
+ 'steelblue': '#4682b4',
+ 'tan': '#d2b48c',
+ 'teal': '#008080',
+ 'thistle': '#d8bfd8',
+ 'tomato': '#ff6347',
+ 'turquoise': '#40e0d0',
+ 'violet': '#ee82ee',
+ 'wheat': '#f5deb3',
+ 'white': '#ffffff',
+ 'whitesmoke': '#f5f5f5',
+ 'yellow': '#ffff00',
+ 'yellowgreen': '#9acd32'}
+
+ def __init__(self, values, mode='rgb', alpha=1.0, wref=_DEFAULT_WREF):
+ '''Instantiate a new grapefruit.Color object.
+
+ Parameters:
+ :values:
+ The values of this color, in the specified representation.
+ :mode:
+ The representation mode used for values.
+ :alpha:
+ the alpha value (transparency) of this color.
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ '''
+ if not(isinstance(values, tuple)):
+ raise TypeError, 'values must be a tuple'
+
+ if mode=='rgb':
+ self.__rgb = values
+ self.__hsl = Color.RgbToHsl(*values)
+ elif mode=='hsl':
+ self.__hsl = values
+ self.__rgb = Color.HslToRgb(*values)
+ else:
+ raise ValueError('Invalid color mode: ' + mode)
+
+ self.__a = alpha
+ self.__wref = wref
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __eq__(self, other):
+ try:
+ if isinstance(other, Color):
+ return (self.__rgb==other.__rgb) and (self.__a==other.__a)
+
+ if len(other) != 4:
+ return False
+ rgba = self.__rgb + (self.__a,)
+ return reduce(lambda x, y: x and (y[0]==y[1]), zip(rgba, other), True)
+ except TypeError:
+ return False
+ except AttributeError:
+ return False
+
+ def __repr__(self):
+ return str(self.__rgb + (self.__a,))
+
+ def __str__(self):
+ '''A string representation of this grapefruit.Color instance.
+
+ Returns:
+ The RGBA representation of this grapefruit.Color instance.
+
+ '''
+ return '(%g, %g, %g, %g)' % (self.__rgb + (self.__a,))
+
+ def __unicode__(self):
+ '''A unicode string representation of this grapefruit.Color instance.
+
+ Returns:
+ The RGBA representation of this grapefruit.Color instance.
+
+ '''
+ return u'(%g, %g, %g, %g)' % (self.__rgb + (self.__a,))
+
+ def __iter__(self):
+ return iter(self.__rgb + (self.__a,))
+
+ def __len__(self):
+ return 4
+
+ @staticmethod
+ def RgbToHsl(r, g, b):
+ '''Convert the color from RGB coordinates to HSL.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (h, s, l) tuple in the range:
+ h[0...360],
+ s[0...1],
+ l[0...1]
+
+ >>> Color.RgbToHsl(1, 0.5, 0)
+ (30.0, 1.0, 0.5)
+
+ '''
+ minVal = min(r, g, b) # min RGB value
+ maxVal = max(r, g, b) # max RGB value
+
+ l = (maxVal + minVal) / 2.0
+ if minVal==maxVal:
+ return (0.0, 0.0, l) # achromatic (gray)
+
+ d = maxVal - minVal # delta RGB value
+
+ if l < 0.5: s = d / (maxVal + minVal)
+ else: s = d / (2.0 - maxVal - minVal)
+
+ dr, dg, db = [(maxVal-val) / d for val in (r, g, b)]
+
+ if r==maxVal:
+ h = db - dg
+ elif g==maxVal:
+ h = 2.0 + dr - db
+ else:
+ h = 4.0 + dg - dr
+
+ h = (h*60.0) % 360.0
+ return (h, s, l)
+
+ @staticmethod
+ def _HueToRgb(n1, n2, h):
+ h %= 6.0
+ if h < 1.0: return n1 + ((n2-n1) * h)
+ if h < 3.0: return n2
+ if h < 4.0: return n1 + ((n2-n1) * (4.0 - h))
+ return n1
+
+ @staticmethod
+ def HslToRgb(h, s, l):
+ '''Convert the color from HSL coordinates to RGB.
+
+ Parameters:
+ :h:
+ The Hue component value [0...1]
+ :s:
+ The Saturation component value [0...1]
+ :l:
+ The Lightness component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> Color.HslToRgb(30.0, 1.0, 0.5)
+ (1.0, 0.5, 0.0)
+
+ '''
+ if s==0: return (l, l, l) # achromatic (gray)
+
+ if l<0.5: n2 = l * (1.0 + s)
+ else: n2 = l+s - (l*s)
+
+ n1 = (2.0 * l) - n2
+
+ h /= 60.0
+ hueToRgb = Color._HueToRgb
+ r = hueToRgb(n1, n2, h + 2)
+ g = hueToRgb(n1, n2, h)
+ b = hueToRgb(n1, n2, h - 2)
+
+ return (r, g, b)
+
+ @staticmethod
+ def RgbToHsv(r, g, b):
+ '''Convert the color from RGB coordinates to HSV.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (h, s, v) tuple in the range:
+ h[0...360],
+ s[0...1],
+ v[0...1]
+
+ >>> Color.RgbToHsv(1, 0.5, 0)
+ (30.0, 1, 1)
+
+ '''
+ v = max(r, g, b)
+ d = v - min(r, g, b)
+ if d==0: return (0.0, 0.0, v)
+ s = d / v
+
+ dr, dg, db = [(v - val) / d for val in (r, g, b)]
+
+ if r==v:
+ h = db - dg # between yellow & magenta
+ elif g==v:
+ h = 2.0 + dr - db # between cyan & yellow
+ else: # b==v
+ h = 4.0 + dg - dr # between magenta & cyan
+
+ h = (h*60.0) % 360.0
+ return (h, s, v)
+
+ @staticmethod
+ def HsvToRgb(h, s, v):
+ '''Convert the color from RGB coordinates to HSV.
+
+ Parameters:
+ :h:
+ The Hus component value [0...1]
+ :s:
+ The Saturation component value [0...1]
+ :v:
+ The Value component [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> Color.HslToRgb(30.0, 1.0, 0.5)
+ (1.0, 0.5, 0.0)
+
+ '''
+ if s==0: return (v, v, v) # achromatic (gray)
+
+ h /= 60.0
+ h = h % 6.0
+
+ i = int(h)
+ f = h - i
+ if not(i&1): f = 1-f # if i is even
+
+ m = v * (1.0 - s)
+ n = v * (1.0 - (s * f))
+
+ if i==0: return (v, n, m)
+ if i==1: return (n, v, m)
+ if i==2: return (m, v, n)
+ if i==3: return (m, n, v)
+ if i==4: return (n, m, v)
+ return (v, m, n)
+
+ @staticmethod
+ def RgbToYiq(r, g, b):
+ '''Convert the color from RGB to YIQ.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (y, i, q) tuple in the range:
+ y[0...1],
+ i[0...1],
+ q[0...1]
+
+ >>> '(%g, %g, %g)' % Color.RgbToYiq(1, 0.5, 0)
+ '(0.592263, 0.458874, -0.0499818)'
+
+ '''
+ y = (r * 0.29895808) + (g * 0.58660979) + (b *0.11443213)
+ i = (r * 0.59590296) - (g * 0.27405705) - (b *0.32184591)
+ q = (r * 0.21133576) - (g * 0.52263517) + (b *0.31129940)
+ return (y, i, q)
+
+ @staticmethod
+ def YiqToRgb(y, i, q):
+ '''Convert the color from YIQ coordinates to RGB.
+
+ Parameters:
+ :y:
+ Tte Y component value [0...1]
+ :i:
+ The I component value [0...1]
+ :q:
+ The Q component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.YiqToRgb(0.592263, 0.458874, -0.0499818)
+ '(1, 0.5, 5.442e-007)'
+
+ '''
+ r = y + (i * 0.9562) + (q * 0.6210)
+ g = y - (i * 0.2717) - (q * 0.6485)
+ b = y - (i * 1.1053) + (q * 1.7020)
+ return (r, g, b)
+
+ @staticmethod
+ def RgbToYuv(r, g, b):
+ '''Convert the color from RGB coordinates to YUV.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (y, u, v) tuple in the range:
+ y[0...1],
+ u[-0.436...0.436],
+ v[-0.615...0.615]
+
+ >>> '(%g, %g, %g)' % Color.RgbToYuv(1, 0.5, 0)
+ '(0.5925, -0.29156, 0.357505)'
+
+ '''
+ y = (r * 0.29900) + (g * 0.58700) + (b * 0.11400)
+ u = -(r * 0.14713) - (g * 0.28886) + (b * 0.43600)
+ v = (r * 0.61500) - (g * 0.51499) - (b * 0.10001)
+ return (y, u, v)
+
+ @staticmethod
+ def YuvToRgb(y, u, v):
+ '''Convert the color from YUV coordinates to RGB.
+
+ Parameters:
+ :y:
+ The Y component value [0...1]
+ :u:
+ The U component value [-0.436...0.436]
+ :v:
+ The V component value [-0.615...0.615]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.YuvToRgb(0.5925, -0.2916, 0.3575)
+ '(0.999989, 0.500015, -6.3276e-005)'
+
+ '''
+ r = y + (v * 1.13983)
+ g = y - (u * 0.39465) - (v * 0.58060)
+ b = y + (u * 2.03211)
+ return (r, g, b)
+
+ @staticmethod
+ def RgbToXyz(r, g, b):
+ '''Convert the color from sRGB to CIE XYZ.
+
+ The methods assumes that the RGB coordinates are given in the sRGB
+ colorspace (D65).
+
+ .. note::
+
+ Compensation for the sRGB gamma correction is applied before converting.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (x, y, z) tuple in the range:
+ x[0...1],
+ y[0...1],
+ z[0...1]
+
+ >>> '(%g, %g, %g)' % Color.RgbToXyz(1, 0.5, 0)
+ '(0.488941, 0.365682, 0.0448137)'
+
+ '''
+ r, g, b = [((v <= 0.03928) and [v / 12.92] or [((v+0.055) / 1.055) **2.4])[0] for v in (r, g, b)]
+
+ x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805)
+ y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722)
+ z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505)
+ return (x, y, z)
+
+ @staticmethod
+ def XyzToRgb(x, y, z):
+ '''Convert the color from CIE XYZ coordinates to sRGB.
+
+ .. note::
+
+ Compensation for sRGB gamma correction is applied before converting.
+
+ Parameters:
+ :x:
+ The X component value [0...1]
+ :y:
+ The Y component value [0...1]
+ :z:
+ The Z component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.XyzToRgb(0.488941, 0.365682, 0.0448137)
+ '(1, 0.5, 6.81883e-008)'
+
+ '''
+ r = (x * 3.2406255) - (y * 1.5372080) - (z * 0.4986286)
+ g = -(x * 0.9689307) + (y * 1.8757561) + (z * 0.0415175)
+ b = (x * 0.0557101) - (y * 0.2040211) + (z * 1.0569959)
+ return tuple((((v <= _srgbGammaCorrInv) and [v * 12.92] or [(1.055 * (v ** (1/2.4))) - 0.055])[0] for v in (r, g, b)))
+
+ @staticmethod
+ def XyzToLab(x, y, z, wref=_DEFAULT_WREF):
+ '''Convert the color from CIE XYZ to CIE L*a*b*.
+
+ Parameters:
+ :x:
+ The X component value [0...1]
+ :y:
+ The Y component value [0...1]
+ :z:
+ The Z component value [0...1]
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ The color as an (L, a, b) tuple in the range:
+ L[0...100],
+ a[-1...1],
+ b[-1...1]
+
+ >>> '(%g, %g, %g)' % Color.XyzToLab(0.488941, 0.365682, 0.0448137)
+ '(66.9518, 0.43084, 0.739692)'
+
+ >>> '(%g, %g, %g)' % Color.XyzToLab(0.488941, 0.365682, 0.0448137, Color.WHITE_REFERENCE['std_D50'])
+ '(66.9518, 0.411663, 0.67282)'
+
+ '''
+ # White point correction
+ x /= wref[0]
+ y /= wref[1]
+ z /= wref[2]
+
+ # Nonlinear distortion and linear transformation
+ x, y, z = [((v > 0.008856) and [v**_oneThird] or [(7.787 * v) + _sixteenHundredsixteenth])[0] for v in (x, y, z)]
+
+ # Vector scaling
+ l = (116 * y) - 16
+ a = 5.0 * (x - y)
+ b = 2.0 * (y - z)
+
+ return (l, a, b)
+
+ @staticmethod
+ def LabToXyz(l, a, b, wref=_DEFAULT_WREF):
+ '''Convert the color from CIE L*a*b* to CIE 1931 XYZ.
+
+ Parameters:
+ :l:
+ The L component [0...100]
+ :a:
+ The a component [-1...1]
+ :b:
+ The a component [-1...1]
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ The color as an (x, y, z) tuple in the range:
+ x[0...q],
+ y[0...1],
+ z[0...1]
+
+ >>> '(%g, %g, %g)' % Color.LabToXyz(66.9518, 0.43084, 0.739692)
+ '(0.488941, 0.365682, 0.0448137)'
+
+ >>> '(%g, %g, %g)' % Color.LabToXyz(66.9518, 0.411663, 0.67282, Color.WHITE_REFERENCE['std_D50'])
+ '(0.488941, 0.365682, 0.0448138)'
+
+ '''
+ y = (l + 16) / 116
+ x = (a / 5.0) + y
+ z = y - (b / 2.0)
+ return tuple((((v > 0.206893) and [v**3] or [(v - _sixteenHundredsixteenth) / 7.787])[0] * w for v, w in zip((x, y, z), wref)))
+
+ @staticmethod
+ def CmykToCmy(c, m, y, k):
+ '''Convert the color from CMYK coordinates to CMY.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+ :k:
+ The Black component value [0...1]
+
+ Returns:
+ The color as an (c, m, y) tuple in the range:
+ c[0...1],
+ m[0...1],
+ y[0...1]
+
+ >>> '(%g, %g, %g)' % Color.CmykToCmy(1, 0.32, 0, 0.5)
+ '(1, 0.66, 0.5)'
+
+ '''
+ mk = 1-k
+ return ((c*mk + k), (m*mk + k), (y*mk + k))
+
+ @staticmethod
+ def CmyToCmyk(c, m, y):
+ '''Convert the color from CMY coordinates to CMYK.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+
+ Returns:
+ The color as an (c, m, y, k) tuple in the range:
+ c[0...1],
+ m[0...1],
+ y[0...1],
+ k[0...1]
+
+ >>> '(%g, %g, %g, %g)' % Color.CmyToCmyk(1, 0.66, 0.5)
+ '(1, 0.32, 0, 0.5)'
+
+ '''
+ k = min(c, m, y)
+ if k==1.0: return (0.0, 0.0, 0.0, 1.0)
+ mk = 1-k
+ return ((c-k) / mk, (m-k) / mk, (y-k) / mk, k)
+
+ @staticmethod
+ def RgbToCmy(r, g, b):
+ '''Convert the color from RGB coordinates to CMY.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (c, m, y) tuple in the range:
+ c[0...1],
+ m[0...1],
+ y[0...1]
+
+ >>> Color.RgbToCmy(1, 0.5, 0)
+ (0, 0.5, 1)
+
+ '''
+ return (1-r, 1-g, 1-b)
+
+ @staticmethod
+ def CmyToRgb(c, m, y):
+ '''Convert the color from CMY coordinates to RGB.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> Color.CmyToRgb(0, 0.5, 1)
+ (1, 0.5, 0)
+
+ '''
+ return (1-c, 1-m, 1-y)
+
+ @staticmethod
+ def RgbToHtml(r, g, b):
+ '''Convert the color from (r, g, b) to #RRGGBB.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ A CSS string representation of this color (#RRGGBB).
+
+ >>> Color.RgbToHtml(1, 0.5, 0)
+ '#ff8000'
+
+ '''
+ return '#%02x%02x%02x' % tuple((min(round(v*255), 255) for v in (r, g, b)))
+
+ @staticmethod
+ def HtmlToRgb(html):
+ '''Convert the HTML color to (r, g, b).
+
+ Parameters:
+ :html:
+ the HTML definition of the color (#RRGGBB or #RGB or a color name).
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ Throws:
+ :ValueError:
+ If html is neither a known color name or a hexadecimal RGB
+ representation.
+
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('#ff8000')
+ '(1, 0.501961, 0)'
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('ff8000')
+ '(1, 0.501961, 0)'
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('#f60')
+ '(1, 0.4, 0)'
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('f60')
+ '(1, 0.4, 0)'
+ >>> '(%g, %g, %g)' % Color.HtmlToRgb('lemonchiffon')
+ '(1, 0.980392, 0.803922)'
+
+ '''
+ html = html.strip().lower()
+ if html[0]=='#':
+ html = html[1:]
+ elif Color.NAMED_COLOR.has_key(html):
+ html = Color.NAMED_COLOR[html][1:]
+
+ if len(html)==6:
+ rgb = html[:2], html[2:4], html[4:]
+ elif len(html)==3:
+ rgb = ['%c%c' % (v,v) for v in html]
+ else:
+ raise ValueError, 'input #%s is not in #RRGGBB format' % html
+
+ return tuple(((int(n, 16) / 255.0) for n in rgb))
+
+ @staticmethod
+ def RgbToPil(r, g, b):
+ '''Convert the color from RGB to a PIL-compatible integer.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ A PIL compatible integer (0xBBGGRR).
+
+ >>> '0x%06x' % Color.RgbToPil(1, 0.5, 0)
+ '0x0080ff'
+
+ '''
+ r, g, b = [min(int(round(v*255)), 255) for v in (r, g, b)]
+ return (b << 16) + (g << 8) + r
+
+ @staticmethod
+ def PilToRgb(pil):
+ '''Convert the color from a PIL-compatible integer to RGB.
+
+ Parameters:
+ pil: a PIL compatible color representation (0xBBGGRR)
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ the range:
+ r: [0...1]
+ g: [0...1]
+ b: [0...1]
+
+ >>> '(%g, %g, %g)' % Color.PilToRgb(0x0080ff)
+ '(1, 0.501961, 0)'
+
+ '''
+ r = 0xff & pil
+ g = 0xff & (pil >> 8)
+ b = 0xff & (pil >> 16)
+ return tuple((v / 255.0 for v in (r, g, b)))
+
+ @staticmethod
+ def _WebSafeComponent(c, alt=False):
+ '''Convert a color component to its web safe equivalent.
+
+ Parameters:
+ :c:
+ The component value [0...1]
+ :alt:
+ If True, return the alternative value instead of the nearest one.
+
+ Returns:
+ The web safe equivalent of the component value.
+
+ '''
+ # This sucks, but floating point between 0 and 1 is quite fuzzy...
+ # So we just change the scale a while to make the equality tests
+ # work, otherwise it gets wrong at some decimal far to the right.
+ sc = c * 100.0
+
+ # If the color is already safe, return it straight away
+ d = sc % 20
+ if d==0: return c
+
+ # Get the lower and upper safe values
+ l = sc - d
+ u = l + 20
+
+ # Return the 'closest' value according to the alt flag
+ if alt:
+ if (sc-l) >= (u-sc): return l/100.0
+ else: return u/100.0
+ else:
+ if (sc-l) >= (u-sc): return u/100.0
+ else: return l/100.0
+
+ @staticmethod
+ def RgbToWebSafe(r, g, b, alt=False):
+ '''Convert the color from RGB to 'web safe' RGB
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+ :alt:
+ If True, use the alternative color instead of the nearest one.
+ Can be used for dithering.
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.RgbToWebSafe(1, 0.55, 0.0)
+ '(1, 0.6, 0)'
+
+ '''
+ webSafeComponent = Color._WebSafeComponent
+ return tuple((webSafeComponent(v, alt) for v in (r, g, b)))
+
+ @staticmethod
+ def RgbToGreyscale(r, g, b):
+ '''Convert the color from RGB to its greyscale equivalent
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+
+ Returns:
+ The color as an (r, g, b) tuple in the range:
+ the range:
+ r[0...1],
+ g[0...1],
+ b[0...1]
+
+ >>> '(%g, %g, %g)' % Color.RgbToGreyscale(1, 0.8, 0)
+ '(0.6, 0.6, 0.6)'
+
+ '''
+ v = (r + g + b) / 3.0
+ return (v, v, v)
+
+ @staticmethod
+ def RgbToRyb(hue):
+ '''Maps a hue on the RGB color wheel to Itten's RYB wheel.
+
+ Parameters:
+ :hue:
+ The hue on the RGB color wheel [0...360]
+
+ Returns:
+ An approximation of the corresponding hue on Itten's RYB wheel.
+
+ >>> Color.RgbToRyb(15)
+ 26
+
+ '''
+ d = hue % 15
+ i = int(hue / 15)
+ x0 = _RybWheel[i]
+ x1 = _RybWheel[i+1]
+ return x0 + (x1-x0) * d / 15
+
+ @staticmethod
+ def RybToRgb(hue):
+ '''Maps a hue on Itten's RYB color wheel to the standard RGB wheel.
+
+ Parameters:
+ :hue:
+ The hue on Itten's RYB color wheel [0...360]
+
+ Returns:
+ An approximation of the corresponding hue on the standard RGB wheel.
+
+ >>> Color.RybToRgb(15)
+ 8
+
+ '''
+ d = hue % 15
+ i = int(hue / 15)
+ x0 = _RgbWheel[i]
+ x1 = _RgbWheel[i+1]
+ return x0 + (x1-x0) * d / 15
+
+ @staticmethod
+ def NewFromRgb(r, g, b, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed RGB values.
+
+ Parameters:
+ :r:
+ The Red component value [0...1]
+ :g:
+ The Green component value [0...1]
+ :b:
+ The Blue component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromRgb(1.0, 0.5, 0.0)
+ (1.0, 0.5, 0.0, 1.0)
+ >>> Color.NewFromRgb(1.0, 0.5, 0.0, 0.5)
+ (1.0, 0.5, 0.0, 0.5)
+
+ '''
+ return Color((r, g, b), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromHsl(h, s, l, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed HSL values.
+
+ Parameters:
+ :h:
+ The Hue component value [0...1]
+ :s:
+ The Saturation component value [0...1]
+ :l:
+ The Lightness component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5)
+ (1.0, 0.5, 0.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5, 0.5)
+ (1.0, 0.5, 0.0, 0.5)
+
+ '''
+ return Color((h, s, l), 'hsl', alpha, wref)
+
+ @staticmethod
+ def NewFromHsv(h, s, v, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed HSV values.
+
+ Parameters:
+ :h:
+ The Hus component value [0...1]
+ :s:
+ The Saturation component value [0...1]
+ :v:
+ The Value component [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsv(30, 1, 1)
+ (1.0, 0.5, 0.0, 1.0)
+ >>> Color.NewFromHsv(30, 1, 1, 0.5)
+ (1.0, 0.5, 0.0, 0.5)
+
+ '''
+ h2, s, l = Color.RgbToHsl(*Color.HsvToRgb(h, s, v))
+ return Color((h, s, l), 'hsl', alpha, wref)
+
+ @staticmethod
+ def NewFromYiq(y, i, q, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed YIQ values.
+
+ Parameters:
+ :y:
+ The Y component value [0...1]
+ :i:
+ The I component value [0...1]
+ :q:
+ The Q component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromYiq(0.5922, 0.45885,-0.05))
+ '(0.999902, 0.499955, -6.6905e-005, 1)'
+ >>> str(Color.NewFromYiq(0.5922, 0.45885,-0.05, 0.5))
+ '(0.999902, 0.499955, -6.6905e-005, 0.5)'
+
+ '''
+ return Color(Color.YiqToRgb(y, i, q), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromYuv(y, u, v, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed YUV values.
+
+ Parameters:
+ :y:
+ The Y component value [0...1]
+ :u:
+ The U component value [-0.436...0.436]
+ :v:
+ The V component value [-0.615...0.615]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromYuv(0.5925, -0.2916, 0.3575))
+ '(0.999989, 0.500015, -6.3276e-005, 1)'
+ >>> str(Color.NewFromYuv(0.5925, -0.2916, 0.3575, 0.5))
+ '(0.999989, 0.500015, -6.3276e-005, 0.5)'
+
+ '''
+ return Color(Color.YuvToRgb(y, u, v), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromXyz(x, y, z, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed CIE-XYZ values.
+
+ Parameters:
+ :x:
+ The Red component value [0...1]
+ :y:
+ The Green component value [0...1]
+ :z:
+ The Blue component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromXyz(0.488941, 0.365682, 0.0448137))
+ '(1, 0.5, 6.81883e-008, 1)'
+ >>> str(Color.NewFromXyz(0.488941, 0.365682, 0.0448137, 0.5))
+ '(1, 0.5, 6.81883e-008, 0.5)'
+
+ '''
+ return Color(Color.XyzToRgb(x, y, z), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromLab(l, a, b, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed CIE-LAB values.
+
+ Parameters:
+ :l:
+ The L component [0...100]
+ :a:
+ The a component [-1...1]
+ :b:
+ The a component [-1...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692))
+ '(1, 0.5, 1.09491e-008, 1)'
+ >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, wref=Color.WHITE_REFERENCE['std_D50']))
+ '(1.01238, 0.492011, -0.14311, 1)'
+ >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, 0.5))
+ '(1, 0.5, 1.09491e-008, 0.5)'
+ >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, 0.5, Color.WHITE_REFERENCE['std_D50']))
+ '(1.01238, 0.492011, -0.14311, 0.5)'
+
+ '''
+ return Color(Color.XyzToRgb(*Color.LabToXyz(l, a, b, wref)), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromCmy(c, m, y, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed CMY values.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromCmy(0, 0.5, 1)
+ (1, 0.5, 0, 1.0)
+ >>> Color.NewFromCmy(0, 0.5, 1, 0.5)
+ (1, 0.5, 0, 0.5)
+
+ '''
+ return Color(Color.CmyToRgb(c, m, y), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromCmyk(c, m, y, k, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed CMYK values.
+
+ Parameters:
+ :c:
+ The Cyan component value [0...1]
+ :m:
+ The Magenta component value [0...1]
+ :y:
+ The Yellow component value [0...1]
+ :k:
+ The Black component value [0...1]
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromCmyk(1, 0.32, 0, 0.5))
+ '(0, 0.34, 0.5, 1)'
+ >>> str(Color.NewFromCmyk(1, 0.32, 0, 0.5, 0.5))
+ '(0, 0.34, 0.5, 0.5)'
+
+ '''
+ return Color(Color.CmyToRgb(*Color.CmykToCmy(c, m, y, k)), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromHtml(html, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed HTML color definition.
+
+ Parameters:
+ :html:
+ The HTML definition of the color (#RRGGBB or #RGB or a color name).
+ :alpha:
+ The color transparency [0...1], default is opaque.
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromHtml('#ff8000'))
+ '(1, 0.501961, 0, 1)'
+ >>> str(Color.NewFromHtml('ff8000'))
+ '(1, 0.501961, 0, 1)'
+ >>> str(Color.NewFromHtml('#f60'))
+ '(1, 0.4, 0, 1)'
+ >>> str(Color.NewFromHtml('f60'))
+ '(1, 0.4, 0, 1)'
+ >>> str(Color.NewFromHtml('lemonchiffon'))
+ '(1, 0.980392, 0.803922, 1)'
+ >>> str(Color.NewFromHtml('#ff8000', 0.5))
+ '(1, 0.501961, 0, 0.5)'
+
+ '''
+ return Color(Color.HtmlToRgb(html), 'rgb', alpha, wref)
+
+ @staticmethod
+ def NewFromPil(pil, alpha=1.0, wref=_DEFAULT_WREF):
+ '''Create a new instance based on the specifed PIL color.
+
+ Parameters:
+ :pil:
+ A PIL compatible color representation (0xBBGGRR)
+ :alpha:
+ The color transparency [0...1], default is opaque
+ :wref:
+ The whitepoint reference, default is 2° D65.
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> str(Color.NewFromPil(0x0080ff))
+ '(1, 0.501961, 0, 1)'
+ >>> str(Color.NewFromPil(0x0080ff, 0.5))
+ '(1, 0.501961, 0, 0.5)'
+
+ '''
+ return Color(Color.PilToRgb(pil), 'rgb', alpha, wref)
+
+ def __GetAlpha(self):
+ return self.__a
+ alpha = property(fget=__GetAlpha, doc='The transparency of this color. 0.0 is transparent and 1.0 is fully opaque.')
+
+ def __GetWRef(self):
+ return self.__wref
+ whiteRef = property(fget=__GetWRef, doc='the white reference point of this color.')
+
+ def __GetRGB(self):
+ return self.__rgb
+ rgb = property(fget=__GetRGB, doc='The RGB values of this Color.')
+
+ def __GetHue(self):
+ return self.__hsl[0]
+ hue = property(fget=__GetHue, doc='The hue of this color.')
+
+ def __GetHSL(self):
+ return self.__hsl
+ hsl = property(fget=__GetHSL, doc='The HSL values of this Color.')
+
+ def __GetHSV(self):
+ h, s, v = Color.RgbToHsv(*self.__rgb)
+ return (self.__hsl[0], s, v)
+ hsv = property(fget=__GetHSV, doc='The HSV values of this Color.')
+
+ def __GetYIQ(self):
+ return Color.RgbToYiq(*self.__rgb)
+ yiq = property(fget=__GetYIQ, doc='The YIQ values of this Color.')
+
+ def __GetYUV(self):
+ return Color.RgbToYuv(*self.__rgb)
+ yuv = property(fget=__GetYUV, doc='The YUV values of this Color.')
+
+ def __GetXYZ(self):
+ return Color.RgbToXyz(*self.__rgb)
+ xyz = property(fget=__GetXYZ, doc='The CIE-XYZ values of this Color.')
+
+ def __GetLAB(self):
+ return Color.XyzToLab(wref=self.__wref, *Color.RgbToXyz(*self.__rgb))
+ lab = property(fget=__GetLAB, doc='The CIE-LAB values of this Color.')
+
+ def __GetCMY(self):
+ return Color.RgbToCmy(*self.__rgb)
+ cmy = property(fget=__GetCMY, doc='The CMY values of this Color.')
+
+ def __GetCMYK(self):
+ return Color.CmyToCmyk(*Color.RgbToCmy(*self.__rgb))
+ cmyk = property(fget=__GetCMYK, doc='The CMYK values of this Color.')
+
+ def __GetHTML(self):
+ return Color.RgbToHtml(*self.__rgb)
+ html = property(fget=__GetHTML, doc='This Color as an HTML color definition.')
+
+ def __GetPIL(self):
+ return Color.RgbToPil(*self.__rgb)
+ pil = property(fget=__GetPIL, doc='This Color as a PIL compatible value.')
+
+ def __GetwebSafe(self):
+ return Color.RgbToWebSafe(*self.__rgb)
+ webSafe = property(fget=__GetwebSafe, doc='The web safe color nearest to this one (RGB).')
+
+ def __GetGreyscale(self):
+ return Color.RgbToGreyscale(*self.rgb)
+ greyscale = property(fget=__GetGreyscale, doc='The greyscale equivalent to this color (RGB).')
+
+ def ColorWithAlpha(self, alpha):
+ '''Create a new instance based on this one with a new alpha value.
+
+ Parameters:
+ :alpha:
+ The transparency of the new color [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromRgb(1.0, 0.5, 0.0, 1.0).ColorWithAlpha(0.5)
+ (1.0, 0.5, 0.0, 0.5)
+
+ '''
+ return Color(self.__rgb, 'rgb', alpha, self.__wref)
+
+ def ColorWithWhiteRef(self, wref, labAsRef=False):
+ '''Create a new instance based on this one with a new white reference.
+
+ Parameters:
+ :wref:
+ The whitepoint reference.
+ :labAsRef:
+ If True, the L*a*b* values of the current instance are used as reference
+ for the new color; otherwise, the RGB values are used as reference.
+
+ Returns:
+ A grapefruit.Color instance.
+
+
+ >>> c = Color.NewFromRgb(1.0, 0.5, 0.0, 1.0, Color.WHITE_REFERENCE['std_D65'])
+
+ >>> c2 = c.ColorWithWhiteRef(Color.WHITE_REFERENCE['sup_D50'])
+ >>> c2.rgb
+ (1.0, 0.5, 0.0)
+ >>> '(%g, %g, %g)' % c2.whiteRef
+ '(0.96721, 1, 0.81428)'
+
+ >>> c2 = c.ColorWithWhiteRef(Color.WHITE_REFERENCE['sup_D50'], labAsRef=True)
+ >>> '(%g, %g, %g)' % c2.rgb
+ '(1.01463, 0.490339, -0.148131)'
+ >>> '(%g, %g, %g)' % c2.whiteRef
+ '(0.96721, 1, 0.81428)'
+ >>> '(%g, %g, %g)' % c.lab
+ '(66.9518, 0.43084, 0.739692)'
+ >>> '(%g, %g, %g)' % c2.lab
+ '(66.9518, 0.43084, 0.739693)'
+
+ '''
+ if labAsRef:
+ l, a, b = self.__GetLAB()
+ return Color.NewFromLab(l, a, b, self.__a, wref)
+ else:
+ return Color(self.__rgb, 'rgb', self.__a, wref)
+
+ def ColorWithHue(self, hue):
+ '''Create a new instance based on this one with a new hue.
+
+ Parameters:
+ :hue:
+ The hue of the new color [0...360].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithHue(60)
+ (1.0, 1.0, 0.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithHue(60).hsl
+ (60, 1, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((hue, s, l), 'hsl', self.__a, self.__wref)
+
+ def ColorWithSaturation(self, saturation):
+ '''Create a new instance based on this one with a new saturation value.
+
+ .. note::
+
+ The saturation is defined for the HSL mode.
+
+ Parameters:
+ :saturation:
+ The saturation of the new color [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithSaturation(0.5)
+ (0.75, 0.5, 0.25, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithSaturation(0.5).hsl
+ (30, 0.5, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, saturation, l), 'hsl', self.__a, self.__wref)
+
+ def ColorWithLightness(self, lightness):
+ '''Create a new instance based on this one with a new lightness value.
+
+ Parameters:
+ :lightness:
+ The lightness of the new color [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithLightness(0.25)
+ (0.5, 0.25, 0.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).ColorWithLightness(0.25).hsl
+ (30, 1, 0.25)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, s, lightness), 'hsl', self.__a, self.__wref)
+
+ def DarkerColor(self, level):
+ '''Create a new instance based on this one but darker.
+
+ Parameters:
+ :level:
+ The amount by which the color should be darkened to produce
+ the new one [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).DarkerColor(0.25)
+ (0.5, 0.25, 0.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).DarkerColor(0.25).hsl
+ (30, 1, 0.25)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, s, max(l - level, 0)), 'hsl', self.__a, self.__wref)
+
+ def LighterColor(self, level):
+ '''Create a new instance based on this one but lighter.
+
+ Parameters:
+ :level:
+ The amount by which the color should be lightened to produce
+ the new one [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).LighterColor(0.25)
+ (1.0, 0.75, 0.5, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).LighterColor(0.25).hsl
+ (30, 1, 0.75)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, s, min(l + level, 1)), 'hsl', self.__a, self.__wref)
+
+ def Saturate(self, level):
+ '''Create a new instance based on this one but more saturated.
+
+ Parameters:
+ :level:
+ The amount by which the color should be saturated to produce
+ the new one [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 0.5, 0.5).Saturate(0.25)
+ (0.875, 0.5, 0.125, 1.0)
+ >>> Color.NewFromHsl(30, 0.5, 0.5).Saturate(0.25).hsl
+ (30, 0.75, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, min(s + level, 1), l), 'hsl', self.__a, self.__wref)
+
+ def Desaturate(self, level):
+ '''Create a new instance based on this one but less saturated.
+
+ Parameters:
+ :level:
+ The amount by which the color should be desaturated to produce
+ the new one [0...1].
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 0.5, 0.5).Desaturate(0.25)
+ (0.625, 0.5, 0.375, 1.0)
+ >>> Color.NewFromHsl(30, 0.5, 0.5).Desaturate(0.25).hsl
+ (30, 0.25, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ return Color((h, max(s - level, 0), l), 'hsl', self.__a, self.__wref)
+
+ def WebSafeDither(self):
+ '''Return the two websafe colors nearest to this one.
+
+ Returns:
+ A tuple of two grapefruit.Color instances which are the two
+ web safe colors closest this one.
+
+ >>> c = Color.NewFromRgb(1.0, 0.45, 0.0)
+ >>> c1, c2 = c.WebSafeDither()
+ >>> str(c1)
+ '(1, 0.4, 0, 1)'
+ >>> str(c2)
+ '(1, 0.6, 0, 1)'
+
+ '''
+ return (
+ Color(Color.RgbToWebSafe(*self.__rgb), 'rgb', self.__a, self.__wref),
+ Color(Color.RgbToWebSafe(alt=True, *self.__rgb), 'rgb', self.__a, self.__wref))
+
+ def Gradient(self, target, steps=100):
+ '''Create a list with the gradient colors between this and the other color.
+
+ Parameters:
+ :target:
+ The grapefruit.Color at the other end of the gradient.
+ :steps:
+ The number of gradients steps to create.
+
+
+ Returns:
+ A list of grapefruit.Color instances.
+
+ >>> c1 = Color.NewFromRgb(1.0, 0.0, 0.0, alpha=1)
+ >>> c2 = Color.NewFromRgb(0.0, 1.0, 0.0, alpha=0)
+ >>> c1.Gradient(c2, 3)
+ [(0.75, 0.25, 0.0, 0.75), (0.5, 0.5, 0.0, 0.5), (0.25, 0.75, 0.0, 0.25)]
+
+ '''
+ gradient = []
+ rgba1 = self.__rgb + (self.__a,)
+ rgba2 = target.__rgb + (target.__a,)
+
+ steps += 1
+ for n in xrange(1, steps):
+ d = 1.0*n/steps
+ r = (rgba1[0]*(1-d)) + (rgba2[0]*d)
+ g = (rgba1[1]*(1-d)) + (rgba2[1]*d)
+ b = (rgba1[2]*(1-d)) + (rgba2[2]*d)
+ a = (rgba1[3]*(1-d)) + (rgba2[3]*d)
+
+ gradient.append(Color((r, g, b), 'rgb', a, self.__wref))
+
+ return gradient
+
+ def ComplementaryColor(self, mode='ryb'):
+ '''Create a new instance which is the complementary color of this one.
+
+ Parameters:
+ :mode:
+ Select which color wheel to use for the generation (ryb/rgb).
+
+
+ Returns:
+ A grapefruit.Color instance.
+
+ >>> Color.NewFromHsl(30, 1, 0.5).ComplementaryColor()
+ (0.0, 0.5, 1.0, 1.0)
+ >>> Color.NewFromHsl(30, 1, 0.5).ComplementaryColor().hsl
+ (210, 1, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+
+ if mode == 'ryb': h = Color.RgbToRyb(h)
+ h = (h+180)%360
+ if mode == 'ryb': h = Color.RybToRgb(h)
+
+ return Color((h, s, l), 'hsl', self.__a, self.__wref)
+
+ def MonochromeScheme(self):
+ '''Return 4 colors in the same hue with varying saturation/lightness.
+
+ Returns:
+ A tuple of 4 grapefruit.Color in the same hue as this one,
+ with varying saturation/lightness.
+
+ >>> c = Color.NewFromHsl(30, 0.5, 0.5)
+ >>> ['(%g, %g, %g)' % clr.hsl for clr in c.MonochromeScheme()]
+ ['(30, 0.2, 0.8)', '(30, 0.5, 0.3)', '(30, 0.2, 0.6)', '(30, 0.5, 0.8)']
+
+ '''
+ def _wrap(x, min, thres, plus):
+ if (x-min) < thres: return x + plus
+ else: return x-min
+
+ h, s, l = self.__hsl
+
+ s1 = _wrap(s, 0.3, 0.1, 0.3)
+ l1 = _wrap(l, 0.5, 0.2, 0.3)
+
+ s2 = s
+ l2 = _wrap(l, 0.2, 0.2, 0.6)
+
+ s3 = s1
+ l3 = max(0.2, l + (1-l)*0.2)
+
+ s4 = s
+ l4 = _wrap(l, 0.5, 0.2, 0.3)
+
+ return (
+ Color((h, s1, l1), 'hsl', self.__a, self.__wref),
+ Color((h, s2, l2), 'hsl', self.__a, self.__wref),
+ Color((h, s3, l3), 'hsl', self.__a, self.__wref),
+ Color((h, s4, l4), 'hsl', self.__a, self.__wref))
+
+ def TriadicScheme(self, angle=120, mode='ryb'):
+ '''Return two colors forming a triad or a split complementary with this one.
+
+ Parameters:
+ :angle:
+ The angle between the hues of the created colors.
+ The default value makes a triad.
+ :mode:
+ Select which color wheel to use for the generation (ryb/rgb).
+
+ Returns:
+ A tuple of two grapefruit.Color forming a color triad with
+ this one or a split complementary.
+
+ >>> c1 = Color.NewFromHsl(30, 1, 0.5)
+
+ >>> c2, c3 = c1.TriadicScheme()
+ >>> c2.hsl
+ (150.0, 1, 0.5)
+ >>> c3.hsl
+ (270.0, 1, 0.5)
+
+ >>> c2, c3 = c1.TriadicScheme(40)
+ >>> c2.hsl
+ (190.0, 1, 0.5)
+ >>> c3.hsl
+ (230.0, 1, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+ angle = min(angle, 120) / 2.0
+
+ if mode == 'ryb': h = Color.RgbToRyb(h)
+ h += 180
+ h1 = (h - angle) % 360
+ h2 = (h + angle) % 360
+ if mode == 'ryb':
+ h1 = Color.RybToRgb(h1)
+ h2 = Color.RybToRgb(h2)
+
+ return (
+ Color((h1, s, l), 'hsl', self.__a, self.__wref),
+ Color((h2, s, l), 'hsl', self.__a, self.__wref))
+
+ def TetradicScheme(self, angle=30, mode='ryb'):
+ '''Return three colors froming a tetrad with this one.
+
+ Parameters:
+ :angle:
+ The angle to substract from the adjacent colors hues [-90...90].
+ You can use an angle of zero to generate a square tetrad.
+ :mode:
+ Select which color wheel to use for the generation (ryb/rgb).
+
+ Returns:
+ A tuple of three grapefruit.Color forming a color tetrad with
+ this one.
+
+ >>> col = Color.NewFromHsl(30, 1, 0.5)
+ >>> [c.hsl for c in col.TetradicScheme(mode='rgb', angle=30)]
+ [(90, 1, 0.5), (210, 1, 0.5), (270, 1, 0.5)]
+
+ '''
+ h, s, l = self.__hsl
+
+ if mode == 'ryb': h = Color.RgbToRyb(h)
+ h1 = (h + 90 - angle) % 360
+ h2 = (h + 180) % 360
+ h3 = (h + 270 - angle) % 360
+ if mode == 'ryb':
+ h1 = Color.RybToRgb(h1)
+ h2 = Color.RybToRgb(h2)
+ h3 = Color.RybToRgb(h3)
+
+ return (
+ Color((h1, s, l), 'hsl', self.__a, self.__wref),
+ Color((h2, s, l), 'hsl', self.__a, self.__wref),
+ Color((h3, s, l), 'hsl', self.__a, self.__wref))
+
+ def AnalogousScheme(self, angle=30, mode='ryb'):
+ '''Return two colors analogous to this one.
+
+ Args:
+ :angle:
+ The angle between the hues of the created colors and this one.
+ :mode:
+ Select which color wheel to use for the generation (ryb/rgb).
+
+ Returns:
+ A tuple of grapefruit.Colors analogous to this one.
+
+ >>> c1 = Color.NewFromHsl(30, 1, 0.5)
+
+ >>> c2, c3 = c1.AnalogousScheme()
+ >>> c2.hsl
+ (330, 1, 0.5)
+ >>> c3.hsl
+ (90, 1, 0.5)
+
+ >>> c2, c3 = c1.AnalogousScheme(10)
+ >>> c2.hsl
+ (20, 1, 0.5)
+ >>> c3.hsl
+ (40, 1, 0.5)
+
+ '''
+ h, s, l = self.__hsl
+
+ if mode == 'ryb': h = Color.RgbToRyb(h)
+ h += 360
+ h1 = (h - angle) % 360
+ h2 = (h + angle) % 360
+ if mode == 'ryb':
+ h1 = Color.RybToRgb(h1)
+ h2 = Color.RybToRgb(h2)
+
+ return (Color((h1, s, l), 'hsl', self.__a, self.__wref),
+ Color((h2, s, l), 'hsl', self.__a, self.__wref))
+
+ def AlphaBlend(self, other):
+ '''Alpha-blend this color on the other one.
+
+ Args:
+ :other:
+ The grapefruit.Color to alpha-blend with this one.
+
+ Returns:
+ A grapefruit.Color instance which is the result of alpha-blending
+ this color on the other one.
+
+ >>> c1 = Color.NewFromRgb(1, 0.5, 0, 0.2)
+ >>> c2 = Color.NewFromRgb(1, 1, 1, 0.8)
+ >>> c3 = c1.AlphaBlend(c2)
+ >>> str(c3)
+ '(1, 0.875, 0.75, 0.84)'
+
+ '''
+ # get final alpha channel
+ fa = self.__a + other.__a - (self.__a * other.__a)
+
+ # get percentage of source alpha compared to final alpha
+ if fa==0: sa = 0
+ else: sa = min(1.0, self.__a/other.__a)
+
+ # destination percentage is just the additive inverse
+ da = 1.0 - sa
+
+ sr, sg, sb = [v * sa for v in self.__rgb]
+ dr, dg, db = [v * da for v in other.__rgb]
+
+ return Color((sr+dr, sg+dg, sb+db), 'rgb', fa, self.__wref)
+
+ def Blend(self, other, percent=0.5):
+ '''Blend this color with the other one.
+
+ Args:
+ :other:
+ the grapefruit.Color to blend with this one.
+
+ Returns:
+ A grapefruit.Color instance which is the result of blending
+ this color on the other one.
+
+ >>> c1 = Color.NewFromRgb(1, 0.5, 0, 0.2)
+ >>> c2 = Color.NewFromRgb(1, 1, 1, 0.6)
+ >>> c3 = c1.Blend(c2)
+ >>> str(c3)
+ '(1, 0.75, 0.5, 0.4)'
+
+ '''
+ dest = 1.0 - percent
+ rgb = tuple(((u * percent) + (v * dest) for u, v in zip(self.__rgb, other.__rgb)))
+ a = (self.__a * percent) + (other.__a * dest)
+ return Color(rgb, 'rgb', a, self.__wref)
+
+def _test():
+ import doctest
+ reload(doctest)
+ doctest.testmod()
+
+if __name__=='__main__':
+ _test()
diff --git a/forum/management/__init__.py b/forum/management/__init__.py
index 60f8f570..e69de29b 100644
--- a/forum/management/__init__.py
+++ b/forum/management/__init__.py
@@ -1,3 +0,0 @@
-#todo: modules
-#from forum.modules import get_modules_script
-#get_modules_script('management')
diff --git a/forum/models/__init__.py b/forum/models/__init__.py
index 5d25da09..1fb18ff9 100644
--- a/forum/models/__init__.py
+++ b/forum/models/__init__.py
@@ -693,11 +693,4 @@ __all__ = [
#'AuthKeyUserAssociation',
'User',
- ]
-
-
-#from forum.modules import get_modules_script_classes
-#for k, v in get_modules_script_classes('models', models.Model).items():
-# if not k in __all__:
-# __all__.append(k)
-# exec "%s = v" % k
+]
diff --git a/forum/modules.py b/forum/modules.py
deleted file mode 100644
index 6c9a9dba..00000000
--- a/forum/modules.py
+++ /dev/null
@@ -1,78 +0,0 @@
-import os
-import types
-import re
-
-from django.template import Template, TemplateDoesNotExist
-
-MODULES_PACKAGE = 'forum_modules'
-
-MODULES_FOLDER = os.path.join(os.path.dirname(__file__), '../' + MODULES_PACKAGE)
-
-MODULE_LIST = [
- __import__('forum_modules.%s' % f, globals(), locals(), ['forum_modules'])
- for f in os.listdir(MODULES_FOLDER)
- if os.path.isdir(os.path.join(MODULES_FOLDER, f)) and
- os.path.exists(os.path.join(MODULES_FOLDER, "%s/__init__.py" % f)) and
- not os.path.exists(os.path.join(MODULES_FOLDER, "%s/DISABLED" % f))
-]
-
-def get_modules_script(script_name):
- all = []
-
- for m in MODULE_LIST:
- try:
- all.append(__import__('%s.%s' % (m.__name__, script_name), globals(), locals(), [m.__name__]))
- except Exception, e:
- #print script_name + ":" + str(e)
- pass
-
- return all
-
-def get_modules_script_classes(script_name, base_class):
- scripts = get_modules_script(script_name)
- all_classes = {}
-
- for script in scripts:
- all_classes.update(dict([
- (n, c) for (n, c) in [(n, getattr(script, n)) for n in dir(script)]
- if isinstance(c, (type, types.ClassType)) and issubclass(c, base_class)
- ]))
-
- return all_classes
-
-def get_all_handlers(name):
- handler_files = get_modules_script('handlers')
-
- return [
- h for h in [
- getattr(f, name) for f in handler_files
- if hasattr(f, name)
- ]
-
- if callable(h)
- ]
-
-def get_handler(name, default):
- all = get_all_handlers(name)
- return len(all) and all[0] or default
-
-module_template_re = re.compile('^modules\/(\w+)\/(.*)$')
-
-def module_templates_loader(name, dirs=None):
- result = module_template_re.search(name)
-
- if result is not None:
- file_name = os.path.join(MODULES_FOLDER, result.group(1), 'templates', result.group(2))
-
- if os.path.exists(file_name):
- try:
- f = open(file_name, 'r')
- source = f.read()
- f.close()
- return (source, file_name)
- except:
- pass
-
- raise TemplateDoesNotExist, name
-
-module_templates_loader.is_usable = True
diff --git a/forum/skins/default/media/js/com.cnprog.i18n.js b/forum/skins/default/media/js/com.cnprog.i18n.js
index 03690dc5..54c3806f 100644
--- a/forum/skins/default/media/js/com.cnprog.i18n.js
+++ b/forum/skins/default/media/js/com.cnprog.i18n.js
@@ -1,225 +1,225 @@
-//var i18nLang;
-var i18nZh = {
- 'insufficient privilege':'用户权限不在操作范围',
- 'cannot pick own answer as best':'不能设置自己的回答为最佳答案',
- 'anonymous users cannot select favorite questions':'匿名用户不能收藏问题,请先',
- 'please login':'注册或者登录',
- 'anonymous users cannot vote':'匿名用户不能投票',
- '>15 points requried to upvote':'需要+15积分才能投支持票。',
- '>100 points required to downvote':'需要+100积分才能投反对票。',
- 'please see': '查看',
- 'cannot vote for own posts':'不能给自己的帖子投票',
- 'daily vote cap exhausted':'对不起,您已用完今日所有的投票。',
- 'cannot revoke old vote':'这个投票已经过时,不能撤销。',
- 'please confirm offensive':"确定要归类该帖为广告、人身攻击、恶意言论吗?",
- 'anonymous users cannot flag offensive posts':'匿名用户不能操作,请先',
- 'cannot flag message as offensive twice':'不能重复操作。',
- 'flag offensive cap exhausted':'对不起,您已用完今日所有的5次‘水帖’操作。',
- 'need >15 points to report spam':"需要+15积分才能归类‘垃圾帖’。",
- 'confirm delete':"确定要删除/撤销删除该帖吗?",
- 'anonymous users cannot delete/undelete':"匿名用户不能删除或撤销删除帖子",
- 'post recovered':"操作成功!该帖子已被恢复。",
- 'post deleted':"操作成功!该帖子已删除。",
- 'add comment':'添加评论',
- 'community karma points':'社区积分',
- 'to comment, need':'评论需要',
- 'delete this comment':'删除此评论',
- 'hide comments':"隐藏评论",
- 'add a comment':"添加评论",
- 'comments':"评论",
- 'confirm delete comment':"真要删除此评论吗?",
- 'characters':'字符',
- 'can write':'还可写',
- 'click to close':'点击消息框关闭',
- 'loading...':'读取中...',
- 'tags cannot be empty':'标签不能为空。',
- 'tablimits info':"最多5个标签,每个标签长度小于20个字符。",
- 'content cannot be empty':'内容不能为空。',
- 'content minchars': '请输入至少 {0} 字符。',
- 'please enter title':'请输入标题。',
- 'title minchars':"请输入至少 {0} 字符。",
- 'delete':'删除',
- 'undelete': '取消',
- 'bold':'粗体',
- 'italic':'斜体',
- 'link':'超链接',
- 'quote':'引用',
- 'preformatted text':'代码',
- 'image':'图片',
- 'numbered list':'数字编号列表',
- 'bulleted list':'项目符号列表',
- 'heading':'标题',
- 'horizontal bar':'水平线',
- 'undo':'撤销',
- 'redo':'重做',
- 'enter image url':'输入图片地址
示例:
http://www.example.com/image.jpg \"我的截图\"',
- 'enter url':'输入Web地址
示例:
http://www.cnprog.com/ \"我的网站\"
"',
- 'upload image':'或者上传本地图片:'
-};
-
-var i18nEn = {
- 'need >15 points to report spam':'need >15 points to report spam ',
- '>15 points requried to upvote':'>15 points required to upvote ',
- 'tags cannot be empty':'please enter at least one tag',
- 'anonymous users cannot vote':'sorry, anonymous users cannot vote ',
- 'anonymous users cannot select favorite questions':'sorry, anonymous users cannot select favorite questions ',
- 'to comment, need': '(to comment other people\'s posts, karma ',
- 'please see':'please see ',
- 'community karma points':' or more is necessary) - ',
- 'upload image':'Upload image:',
- 'enter image url':'enter URL of the image, e.g. http://www.example.com/image.jpg \"image title\"',
- 'enter url':'enter Web address, e.g. http://www.example.com \"page title\"',
- 'daily vote cap exhausted':'sorry, you\'ve used up todays vote cap',
- 'cannot pick own answer as best':'sorry, you cannot accept your own answer',
- 'cannot revoke old vote':'sorry, older votes cannot be revoked',
- 'please confirm offensive':'are you sure this post is offensive, contains spam, advertising, malicious remarks, etc.?',
- 'flag offensive cap exhausted':'sorry, you\'ve used up todays cap of flagging offensive messages ',
- 'confirm delete':'are you sure you want to delete this?',
- 'anonymous users cannot delete/undelete':'sorry, anonymous users cannot delete or undelete posts',
- 'post recovered':'your post is now restored!',
- 'post deleted':'your post has been deleted',
- 'confirm delete comment':'do you really want to delete this comment?',
- 'can write':'have ',
- 'tablimits info':'up to 5 tags, no more than 20 characters each',
- 'content minchars': 'please enter more than {0} characters',
- 'title minchars':"please enter at least {0} characters",
- 'characters':'characters left',
- 'cannot vote for own posts':'sorry, you cannot vote for your own posts',
- 'cannot flag message as offensive twice':'cannot flag message as offensive twice ',
- '>100 points required to downvote':'>100 points required to downvote '
-};
-
-var i18nTr = {
- 'insufficient privilege':'buna yetkiniz yoktur',
- 'cannot pick own answer as best':'en cevap olarak kendi cevabınızı seçemezsiniz',
- 'anonymous users cannot select favorite questions':'üye girişi yapmadan favori seçemezsiniz',
- 'please login':'lütfen üye girişi yapınız',
- 'anonymous users cannot vote':'üye girişi yapmadan oy kullanamazsınız',
- '>15 points requried to upvote': 'beğeninizi göstermek için en az 15 puan toplamalısınız',
- '>100 points required to downvote':'beğenmediğinizi göstermek için en az 100 puan toplamalısınız',
- 'please see': 'lütfen bakın',
- 'cannot vote for own posts':'kendi yazılarınıza oy veremezsiniz',
- 'daily vote cap exhausted':'bugünlük oy verme kotanız doldu',
- 'cannot revoke old vote':'verilen bir oyu iptal edemezsiniz',
- 'please confirm offensive':"şikayetinizi onaylayın",
- 'anonymous users cannot flag offensive posts':'üye girişi yapmadan şikayet gönderemezsiniz',
- 'cannot flag message as offensive twice':'şikayet mesajı olarak iki kez işaretlemelisiniz',
- 'flag offensive cap exhausted':'şikayet kotası aşıldı',
- 'need >15 points to report spam':"spam olarak bildirmek için an az 15 puanınız olmalı",
- 'confirm delete':"Bunu silmek istediğinizden emin misiniz?",
- 'anonymous users cannot delete/undelete':"üye girişi yapmadan yazı silemez yada geri alamazsınız",
- 'post recovered':"yazı geri alındı",
- 'post deleted':"yazı silindi",
- 'add comment':'yorum ekle',
- 'community karma points':'site itibar puanları',
- 'to comment, need':'Yorum için itibar puanınız olmalı',
- 'delete this comment':'bu yorumu sil',
- 'hide comments':"yorumları gizle",
- 'add a comment':"yorum ekle",
- 'comments':"yorumlar",
- 'confirm delete comment':"yorumu silmek istediğinizden emin misiniz?",
- 'characters':'karakter eksik',
- 'can write':'yazılabilir ',
- 'click to close':'kapatmak için tıklayın',
- 'loading...':'yükleniyor...',
- 'tags cannot be empty':'etiketler boş olamaz',
- 'tablimits info':"En fazla 5 etiket ve her biri en fazla 20 karakter",
- 'content cannot be empty':'içerik boş olamaz',
- 'content minchars': 'Lütfen en az (0) karakter girin',
- 'please enter title':'lütfen bir başlık yazın',
- 'title minchars':"Lütfen en az (0) karakter girin",
- 'delete':'sil',
- 'undelete': 'geri al',
- 'bold': 'kalın',
- 'italic':'italik',
- 'link':'link',
- 'quote':'alıntı',
- 'preformatted text':'hazır metin',
- 'image':'resimler',
- 'numbered list':'numaralı liste',
- 'bulleted list':'işaretli liste',
- 'heading':'Başlık',
- 'horizontal bar':'yatay bar',
- 'undo':'geri',
- 'redo':'yeniden',
- 'enter image url':'örnek resmin URLsini girin:
http://www.example.com/image.jpg \"resim başlığı\"',
- 'enter url':'web adresini girin:
http://www.cnprog.com/ \"başlık bağlantısı\""',
- 'upload image':'resim yükle:',
- 'questions/' : 'sorular/',
- 'answers/' : 'cevaplar/',
- 'comments/' : 'yorumlar/',
- 'vote/' : 'oy/',
- 'delete/' : 'sil/'
-};
-
-var i18nEs = {
- 'insufficient privilege':'privilegio insuficiente',
- 'cannot pick own answer as best':'no puede escoger su propia respuesta como la mejor',
- 'anonymous users cannot select favorite questions':'usuarios anonimos no pueden seleccionar',
- 'please login':'por favor inicie sesión',
- 'anonymous users cannot vote':'usuarios anónimos no pueden votar',
- '>15 points requried to upvote': '>15 puntos requeridos para votar positivamente',
- '>100 points required to downvote':'>100 puntos requeridos para votar negativamente',
- 'please see': 'por favor vea',
- 'cannot vote for own posts':'no se puede votar por sus propias publicaciones',
- 'daily vote cap exhausted':'cuota de votos diarios excedida',
- 'cannot revoke old vote':'no puede revocar un voto viejo',
- 'please confirm offensive':"por favor confirme ofensiva",
- 'anonymous users cannot flag offensive posts':'usuarios anónimos no pueden marcar publicaciones como ofensivas',
- 'cannot flag message as offensive twice':'no puede marcar mensaje como ofensivo dos veces',
- 'flag offensive cap exhausted':'cuota para marcar ofensivas ha sido excedida',
- 'need >15 points to report spam':"necesita >15 puntos para reportar spam",
- 'confirm delete':"¿Está seguro que desea borrar esto?",
- 'anonymous users cannot delete/undelete':"usuarios anónimos no pueden borrar o recuperar publicaciones",
- 'post recovered':"publicación recuperada",
- 'post deleted':"publicación borrada。",
- 'add comment':'agregar comentario',
- 'community karma points':'reputación comunitaria',
- 'to comment, need':'para comentar, necesita reputación',
- 'delete this comment':'borrar este comentario',
- 'hide comments':"ocultar comentarios",
- 'add a comment':"agregar comentarios",
- 'comments':"comentarios",
- 'confirm delete comment':"¿Realmente desea borrar este comentario?",
- 'characters':'caracteres faltantes',
- 'can write':'tiene ',
- 'click to close':'haga click para cerrar',
- 'loading...':'cargando...',
- 'tags cannot be empty':'las etiquetas no pueden estar vacías',
- 'tablimits info':"hasta 5 etiquetas de no mas de 20 caracteres cada una",
- 'content cannot be empty':'el contenido no puede estar vacío',
- 'content minchars': 'por favor introduzca mas de {0} caracteres',
- 'please enter title':'por favor ingrese un título',
- 'title minchars':"por favor introduzca al menos {0} caracteres",
- 'delete':'borrar',
- 'undelete': 'recuperar',
- 'bold': 'negrita',
- 'italic':'cursiva',
- 'link':'enlace',
- 'quote':'citar',
- 'preformatted text':'texto preformateado',
- 'image':'imagen',
- 'numbered list':'lista numerada',
- 'bulleted list':'lista no numerada',
- 'heading':'标题',
- 'horizontal bar':'barra horizontal',
- 'undo':'deshacer',
- 'redo':'rehacer',
- 'enter image url':'introduzca la URL de la imagen, por ejemplo:
http://www.example.com/image.jpg \"titulo de imagen\"',
- 'enter url':'introduzca direcciones web, ejemplo:
http://www.cnprog.com/ \"titulo del enlace\""',
- 'upload image':'cargar imagen:',
- 'questions/' : 'preguntas/',
- 'answers/' : 'respuestas/',
- 'comments/' : 'comentarios/',
- 'vote/' : 'votar/',
- 'delete/' : 'eliminar/'
-};
-
-var i18n = {
- 'en':i18nEn,
- 'zh-cn':i18nZh,
- 'es':i18nEs,
- 'tr':i18nTr
-};
-
-var i18n_dict = i18n[i18nLang];
+//var i18nLang;
+var i18nZh = {
+ 'insufficient privilege':'用户权限不在操作范围',
+ 'cannot pick own answer as best':'不能设置自己的回答为最佳答案',
+ 'anonymous users cannot select favorite questions':'匿名用户不能收藏问题,请先',
+ 'please login':'注册或者登录',
+ 'anonymous users cannot vote':'匿名用户不能投票',
+ '>15 points requried to upvote':'需要+15积分才能投支持票。',
+ '>100 points required to downvote':'需要+100积分才能投反对票。',
+ 'please see': '查看',
+ 'cannot vote for own posts':'不能给自己的帖子投票',
+ 'daily vote cap exhausted':'对不起,您已用完今日所有的投票。',
+ 'cannot revoke old vote':'这个投票已经过时,不能撤销。',
+ 'please confirm offensive':"确定要归类该帖为广告、人身攻击、恶意言论吗?",
+ 'anonymous users cannot flag offensive posts':'匿名用户不能操作,请先',
+ 'cannot flag message as offensive twice':'不能重复操作。',
+ 'flag offensive cap exhausted':'对不起,您已用完今日所有的5次‘水帖’操作。',
+ 'need >15 points to report spam':"需要+15积分才能归类‘垃圾帖’。",
+ 'confirm delete':"确定要删除/撤销删除该帖吗?",
+ 'anonymous users cannot delete/undelete':"匿名用户不能删除或撤销删除帖子",
+ 'post recovered':"操作成功!该帖子已被恢复。",
+ 'post deleted':"操作成功!该帖子已删除。",
+ 'add comment':'添加评论',
+ 'community karma points':'社区积分',
+ 'to comment, need':'评论需要',
+ 'delete this comment':'删除此评论',
+ 'hide comments':"隐藏评论",
+ 'add a comment':"添加评论",
+ 'comments':"评论",
+ 'confirm delete comment':"真要删除此评论吗?",
+ 'characters':'字符',
+ 'can write':'还可写',
+ 'click to close':'点击消息框关闭',
+ 'loading...':'读取中...',
+ 'tags cannot be empty':'标签不能为空。',
+ 'tablimits info':"最多5个标签,每个标签长度小于20个字符。",
+ 'content cannot be empty':'内容不能为空。',
+ 'content minchars': '请输入至少 {0} 字符。',
+ 'please enter title':'请输入标题。',
+ 'title minchars':"请输入至少 {0} 字符。",
+ 'delete':'删除',
+ 'undelete': '取消',
+ 'bold':'粗体',
+ 'italic':'斜体',
+ 'link':'超链接',
+ 'quote':'引用',
+ 'preformatted text':'代码',
+ 'image':'图片',
+ 'numbered list':'数字编号列表',
+ 'bulleted list':'项目符号列表',
+ 'heading':'标题',
+ 'horizontal bar':'水平线',
+ 'undo':'撤销',
+ 'redo':'重做',
+ 'enter image url':'输入图片地址示例:
http://www.example.com/image.jpg \"我的截图\"',
+ 'enter url':'输入Web地址
示例:
http://www.cnprog.com/ \"我的网站\"
"',
+ 'upload image':'或者上传本地图片:'
+};
+
+var i18nEn = {
+ 'need >15 points to report spam':'need >15 points to report spam ',
+ '>15 points requried to upvote':'>15 points required to upvote ',
+ 'tags cannot be empty':'please enter at least one tag',
+ 'anonymous users cannot vote':'sorry, anonymous users cannot vote ',
+ 'anonymous users cannot select favorite questions':'sorry, anonymous users cannot select favorite questions ',
+ 'to comment, need': '(to comment other people\'s posts, karma ',
+ 'please see':'please see ',
+ 'community karma points':' or more is necessary) - ',
+ 'upload image':'Upload image:',
+ 'enter image url':'enter URL of the image, e.g. http://www.example.com/image.jpg \"image title\"',
+ 'enter url':'enter Web address, e.g. http://www.example.com \"page title\"',
+ 'daily vote cap exhausted':'sorry, you\'ve used up todays vote cap',
+ 'cannot pick own answer as best':'sorry, you cannot accept your own answer',
+ 'cannot revoke old vote':'sorry, older votes cannot be revoked',
+ 'please confirm offensive':'are you sure this post is offensive, contains spam, advertising, malicious remarks, etc.?',
+ 'flag offensive cap exhausted':'sorry, you\'ve used up todays cap of flagging offensive messages ',
+ 'confirm delete':'are you sure you want to delete this?',
+ 'anonymous users cannot delete/undelete':'sorry, anonymous users cannot delete or undelete posts',
+ 'post recovered':'your post is now restored!',
+ 'post deleted':'your post has been deleted',
+ 'confirm delete comment':'do you really want to delete this comment?',
+ 'can write':'have ',
+ 'tablimits info':'up to 5 tags, no more than 20 characters each',
+ 'content minchars': 'please enter more than {0} characters',
+ 'title minchars':"please enter at least {0} characters",
+ 'characters':'characters left',
+ 'cannot vote for own posts':'sorry, you cannot vote for your own posts',
+ 'cannot flag message as offensive twice':'cannot flag message as offensive twice ',
+ '>100 points required to downvote':'>100 points required to downvote '
+};
+
+var i18nTr = {
+ 'insufficient privilege':'buna yetkiniz yoktur',
+ 'cannot pick own answer as best':'en cevap olarak kendi cevabınızı seçemezsiniz',
+ 'anonymous users cannot select favorite questions':'üye girişi yapmadan favori seçemezsiniz',
+ 'please login':'lütfen üye girişi yapınız',
+ 'anonymous users cannot vote':'üye girişi yapmadan oy kullanamazsınız',
+ '>15 points requried to upvote': 'beğeninizi göstermek için en az 15 puan toplamalısınız',
+ '>100 points required to downvote':'beğenmediğinizi göstermek için en az 100 puan toplamalısınız',
+ 'please see': 'lütfen bakın',
+ 'cannot vote for own posts':'kendi yazılarınıza oy veremezsiniz',
+ 'daily vote cap exhausted':'bugünlük oy verme kotanız doldu',
+ 'cannot revoke old vote':'verilen bir oyu iptal edemezsiniz',
+ 'please confirm offensive':"şikayetinizi onaylayın",
+ 'anonymous users cannot flag offensive posts':'üye girişi yapmadan şikayet gönderemezsiniz',
+ 'cannot flag message as offensive twice':'şikayet mesajı olarak iki kez işaretlemelisiniz',
+ 'flag offensive cap exhausted':'şikayet kotası aşıldı',
+ 'need >15 points to report spam':"spam olarak bildirmek için an az 15 puanınız olmalı",
+ 'confirm delete':"Bunu silmek istediğinizden emin misiniz?",
+ 'anonymous users cannot delete/undelete':"üye girişi yapmadan yazı silemez yada geri alamazsınız",
+ 'post recovered':"yazı geri alındı",
+ 'post deleted':"yazı silindi",
+ 'add comment':'yorum ekle',
+ 'community karma points':'site itibar puanları',
+ 'to comment, need':'Yorum için itibar puanınız olmalı',
+ 'delete this comment':'bu yorumu sil',
+ 'hide comments':"yorumları gizle",
+ 'add a comment':"yorum ekle",
+ 'comments':"yorumlar",
+ 'confirm delete comment':"yorumu silmek istediğinizden emin misiniz?",
+ 'characters':'karakter eksik',
+ 'can write':'yazılabilir ',
+ 'click to close':'kapatmak için tıklayın',
+ 'loading...':'yükleniyor...',
+ 'tags cannot be empty':'etiketler boş olamaz',
+ 'tablimits info':"En fazla 5 etiket ve her biri en fazla 20 karakter",
+ 'content cannot be empty':'içerik boş olamaz',
+ 'content minchars': 'Lütfen en az (0) karakter girin',
+ 'please enter title':'lütfen bir başlık yazın',
+ 'title minchars':"Lütfen en az (0) karakter girin",
+ 'delete':'sil',
+ 'undelete': 'geri al',
+ 'bold': 'kalın',
+ 'italic':'italik',
+ 'link':'link',
+ 'quote':'alıntı',
+ 'preformatted text':'hazır metin',
+ 'image':'resimler',
+ 'numbered list':'numaralı liste',
+ 'bulleted list':'işaretli liste',
+ 'heading':'Başlık',
+ 'horizontal bar':'yatay bar',
+ 'undo':'geri',
+ 'redo':'yeniden',
+ 'enter image url':'örnek resmin URLsini girin:
http://www.example.com/image.jpg \"resim başlığı\"',
+ 'enter url':'web adresini girin:
http://www.cnprog.com/ \"başlık bağlantısı\""',
+ 'upload image':'resim yükle:',
+ 'questions/' : 'sorular/',
+ 'answers/' : 'cevaplar/',
+ 'comments/' : 'yorumlar/',
+ 'vote/' : 'oy/',
+ 'delete/' : 'sil/'
+};
+
+var i18nEs = {
+ 'insufficient privilege':'privilegio insuficiente',
+ 'cannot pick own answer as best':'no puede escoger su propia respuesta como la mejor',
+ 'anonymous users cannot select favorite questions':'usuarios anonimos no pueden seleccionar',
+ 'please login':'por favor inicie sesión',
+ 'anonymous users cannot vote':'usuarios anónimos no pueden votar',
+ '>15 points requried to upvote': '>15 puntos requeridos para votar positivamente',
+ '>100 points required to downvote':'>100 puntos requeridos para votar negativamente',
+ 'please see': 'por favor vea',
+ 'cannot vote for own posts':'no se puede votar por sus propias publicaciones',
+ 'daily vote cap exhausted':'cuota de votos diarios excedida',
+ 'cannot revoke old vote':'no puede revocar un voto viejo',
+ 'please confirm offensive':"por favor confirme ofensiva",
+ 'anonymous users cannot flag offensive posts':'usuarios anónimos no pueden marcar publicaciones como ofensivas',
+ 'cannot flag message as offensive twice':'no puede marcar mensaje como ofensivo dos veces',
+ 'flag offensive cap exhausted':'cuota para marcar ofensivas ha sido excedida',
+ 'need >15 points to report spam':"necesita >15 puntos para reportar spam",
+ 'confirm delete':"¿Está seguro que desea borrar esto?",
+ 'anonymous users cannot delete/undelete':"usuarios anónimos no pueden borrar o recuperar publicaciones",
+ 'post recovered':"publicación recuperada",
+ 'post deleted':"publicación borrada。",
+ 'add comment':'agregar comentario',
+ 'community karma points':'reputación comunitaria',
+ 'to comment, need':'para comentar, necesita reputación',
+ 'delete this comment':'borrar este comentario',
+ 'hide comments':"ocultar comentarios",
+ 'add a comment':"agregar comentarios",
+ 'comments':"comentarios",
+ 'confirm delete comment':"¿Realmente desea borrar este comentario?",
+ 'characters':'caracteres faltantes',
+ 'can write':'tiene ',
+ 'click to close':'haga click para cerrar',
+ 'loading...':'cargando...',
+ 'tags cannot be empty':'las etiquetas no pueden estar vacías',
+ 'tablimits info':"hasta 5 etiquetas de no mas de 20 caracteres cada una",
+ 'content cannot be empty':'el contenido no puede estar vacío',
+ 'content minchars': 'por favor introduzca mas de {0} caracteres',
+ 'please enter title':'por favor ingrese un título',
+ 'title minchars':"por favor introduzca al menos {0} caracteres",
+ 'delete':'borrar',
+ 'undelete': 'recuperar',
+ 'bold': 'negrita',
+ 'italic':'cursiva',
+ 'link':'enlace',
+ 'quote':'citar',
+ 'preformatted text':'texto preformateado',
+ 'image':'imagen',
+ 'numbered list':'lista numerada',
+ 'bulleted list':'lista no numerada',
+ 'heading':'标题',
+ 'horizontal bar':'barra horizontal',
+ 'undo':'deshacer',
+ 'redo':'rehacer',
+ 'enter image url':'introduzca la URL de la imagen, por ejemplo:
http://www.example.com/image.jpg \"titulo de imagen\"',
+ 'enter url':'introduzca direcciones web, ejemplo:
http://www.cnprog.com/ \"titulo del enlace\""',
+ 'upload image':'cargar imagen:',
+ 'questions/' : 'preguntas/',
+ 'answers/' : 'respuestas/',
+ 'comments/' : 'comentarios/',
+ 'vote/' : 'votar/',
+ 'delete/' : 'eliminar/'
+};
+
+var i18n = {
+ 'en':i18nEn,
+ 'zh-cn':i18nZh,
+ 'es':i18nEs,
+ 'tr':i18nTr
+};
+
+var i18n_dict = i18n[i18nLang];
diff --git a/forum/skins/default/media/style/style.css b/forum/skins/default/media/style/style.css
index 1ae117c4..9d34a26e 100755
--- a/forum/skins/default/media/style/style.css
+++ b/forum/skins/default/media/style/style.css
@@ -229,7 +229,7 @@ blockquote {
}
#CARight {
- width: 240px;
+ width: 235px;
float: right;
}
@@ -272,7 +272,7 @@ blockquote {
}
#top a.ab-responses-envelope {
- margin-left: 0;
+ margin-left: 3px;
}
#top a img {
vertical-align:middle;
@@ -288,7 +288,7 @@ blockquote {
#logoContainer {
}
#navTabContainer {
- width: 600px;
+ width: 610px;
padding-left: 10px;
text-align: left;
}
@@ -371,7 +371,7 @@ blockquote {
line-height: 24px;
height: 36px;
width: 605px;
- margin: 0px;
+ margin: 0px 3px 0px 0px;
padding: 5px 0 0 5px;
}
@@ -381,7 +381,7 @@ blockquote {
height: 36px;
width: 561px;
padding: 5px 0 0 5px;
- margin: 0px;
+ margin: 0px 3px 0px 0px;
}
#searchBar .searchBtn {
@@ -392,8 +392,8 @@ blockquote {
width: 80px;
width: 80px;
line-height: 22px;
+ margin: 0px;
text-align: center;
- margin-top:1px;
padding-bottom: 4px;
}
@@ -403,8 +403,8 @@ blockquote {
height: 40px;
width: 40px;
line-height: 22px;
+ margin: 0px 3px 0px 0px;
padding-bottom: 4px;
- margin-top:1px;
text-align: center;
}
@@ -694,7 +694,6 @@ blockquote {
background: white /*#cacdc6; /*f9f7ed;*/
padding: 10px;
margin-bottom: 8px;
- margin-left: 10px;
/*
border-top: 1px solid #eeeeec;
border-left: 1px solid #eeeeec;
@@ -833,7 +832,7 @@ conflicts with WMD!
.tags a {
white-space: nowrap;
- font-size: 13px;
+ font-size: 10px;
font-weight: normal;
color: #333;
text-decoration: none;
@@ -843,6 +842,7 @@ conflicts with WMD!
border-bottom: 1px solid #CCC;
border-right: 1px solid #CCC;
padding: 1px 8px 1px 8px;
+ margin-right:3px;
}
.tags a:hover {
diff --git a/forum/skins/default/templates/question_list.html b/forum/skins/default/templates/question_list.html
index 38ac254a..c9fc1f96 100644
--- a/forum/skins/default/templates/question_list.html
+++ b/forum/skins/default/templates/question_list.html
@@ -11,7 +11,7 @@
diff --git a/forum/skins/default/templates/user_responses.html b/forum/skins/default/templates/user_responses.html
index 21f08046..2d96112b 100644
--- a/forum/skins/default/templates/user_responses.html
+++ b/forum/skins/default/templates/user_responses.html
@@ -28,8 +28,8 @@ response_snippet - abbreviated content of the response
{{ response.user.username }}
- {{ response.response_type }},
- {% diff_date response.timestamp 3 %}:
+ {{ response.response_type }}
+ ({% diff_date response.timestamp 3 "True" %}):
"{{ response.response_title }}"
{{ response.response_snippet|safe }}
diff --git a/forum/sql_scripts/update_2010_01_23.sql b/forum/sql_scripts/update_2010_01_23.sql
deleted file mode 100755
index 621207be..00000000
--- a/forum/sql_scripts/update_2010_01_23.sql
+++ /dev/null
@@ -1,9 +0,0 @@
-CREATE TABLE `fbconnect_fbassociation` (
- `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
- `user_id` integer NOT NULL,
- `fbuid` varchar(12) NOT NULL UNIQUE
-)
-;
-ALTER TABLE `fbconnect_fbassociation` ADD CONSTRAINT `user_id_refs_id_3534873d`
-FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`);
-CREATE INDEX `fbconnect_fbassociation_user_id` ON `fbconnect_fbassociation` (`user_id`);
diff --git a/forum/templatetags/extra_filters.py b/forum/templatetags/extra_filters.py
index 2da313db..8c023810 100644
--- a/forum/templatetags/extra_filters.py
+++ b/forum/templatetags/extra_filters.py
@@ -1,6 +1,6 @@
from django import template
from forum import auth
-from forum_modules.grapefruit import Color
+from forum.deps.grapefruit import Color
from django.utils.translation import ugettext as _
import logging
diff --git a/forum/templatetags/extra_tags.py b/forum/templatetags/extra_tags.py
index 15f97ac8..960870a0 100644
--- a/forum/templatetags/extra_tags.py
+++ b/forum/templatetags/extra_tags.py
@@ -301,7 +301,7 @@ def convert2tagname_list(question):
return ''
@register.simple_tag
-def diff_date(date, limen=2):
+def diff_date(date, limen=2, use_on_prefix = False):
now = datetime.datetime.now()#datetime(*time.localtime()[0:6])#???
diff = now - date
days = diff.days
@@ -310,9 +310,13 @@ def diff_date(date, limen=2):
if days > 2:
if date.year == now.year:
- return date.strftime("%b %d")# at %H:%M")
+ date_token = date.strftime("%b %d")
else:
- return date.strftime("%b %d '%y")# at %H:%M")
+ date_token = date.strftime("%b %d '%y")
+ if use_on_prefix:
+ return _('on %(date)s') % { 'date': date_token }
+ else:
+ return date_token
elif days == 2:
return _('2 days ago')
elif days == 1:
diff --git a/forum/tests.py b/forum/tests.py
index 28544b73..4f0482f5 100644
--- a/forum/tests.py
+++ b/forum/tests.py
@@ -614,7 +614,7 @@ class UpdateNotificationTests(TestCase):
class AnonymousVisitorTests(TestCase):
- fixtures = ['forum/fixtures/full_dump.json', ]
+ fixtures = ['tmp/fixture1.json', ]
def test_index(self):
#todo: merge this with all reader url tests
diff --git a/forum/utils/colors.py b/forum/utils/colors.py
index 694cc3b0..f7bee01e 100644
--- a/forum/utils/colors.py
+++ b/forum/utils/colors.py
@@ -1,4 +1,4 @@
-from forum_modules.grapefruit import Color
+from forum.deps.grapefruit import Color
import math
def get_counter_colors(count, counter_max=10, empty_bg='white', empty_fg='black',
diff --git a/forum/views/users.py b/forum/views/users.py
index 42d406c2..0995812e 100644
--- a/forum/views/users.py
+++ b/forum/views/users.py
@@ -637,8 +637,8 @@ def user_responses(request, user_id, user_view):
page
"""
user = get_object_or_404(models.User, id=user_id)
- #if request.user != user:
- # raise Http404
+ if request.user != user:
+ raise Http404
user = get_object_or_404(models.User, id=user_id)
response_list = []
diff --git a/forum_modules/__init__.py b/forum_modules/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/forum_modules/authentication/README b/forum_modules/authentication/README
deleted file mode 100644
index a602dc2c..00000000
--- a/forum_modules/authentication/README
+++ /dev/null
@@ -1,3 +0,0 @@
-THIS DIRECTORY IS NOT USED
-
-authentication module will be redone as separate application
diff --git a/forum_modules/authentication/auth.py b/forum_modules/authentication/auth.py
deleted file mode 100644
index b46e3df3..00000000
--- a/forum_modules/authentication/auth.py
+++ /dev/null
@@ -1,144 +0,0 @@
-from django.shortcuts import render_to_response, get_object_or_404
-from django.template import RequestContext
-from django.core.urlresolvers import reverse
-from django.contrib.auth.models import User
-from django.http import HttpResponseRedirect, Http404
-from django.utils.safestring import mark_safe
-from django.utils.translation import ugettext as _
-from django.utils.http import urlquote_plus
-from django.contrib.auth.decorators import login_required
-from django.contrib.auth import login, logout
-from django.http import get_host
-import types
-import datetime
-
-from forum.models import AuthKeyUserAssociation, ValidationHash
-from forum.authentication.forms import SimpleRegistrationForm, SimpleEmailSubscribeForm, \
- TemporaryLoginRequestForm, ChangePasswordForm, SetPasswordForm
-from forum.utils.email import send_email
-
-from forum.authentication.base import InvalidAuthentication
-from forum.authentication import AUTH_PROVIDERS
-
-from forum.models import Question, Answer
-
-def send_validation_email(user):
- hash = ValidationHash.objects.create_new(user, 'email', [user.email])
- send_email(_("Email Validation"), [user.email], "auth/email_validation.html", {
- 'validation_code': hash,
- 'user': user
- })
-
-def validate_email(request, user, code):
- user = get_object_or_404(User, id=user)
-
- if (ValidationHash.objects.validate(code, user, 'email', [user.email])):
- user.email_isvalid = True
- user.save()
- return login_and_forward(request, user, None, _("Thank you, your email is now validated."))
- else:
- raise Http404()
-
-@login_required
-def auth_settings(request):
- """
- change password view.
-
- url : /changepw/
- template: authopenid/changepw.html
- """
- user_ = request.user
- auth_keys = user_.auth_keys.all()
-
- if user_.has_usable_password():
- FormClass = ChangePasswordForm
- else:
- FormClass = SetPasswordForm
-
- if request.POST:
- form = FormClass(request.POST, user=user_)
- if form.is_valid():
- if user_.has_usable_password():
- request.user.message_set.create(message=_("Your password was changed"))
- else:
- request.user.message_set.create(message=_("New password set"))
- FormClass = ChangePasswordForm
-
- user_.set_password(form.cleaned_data['password1'])
- user_.save()
- return HttpResponseRedirect(reverse('user_authsettings'))
-
- form = FormClass(user=user_)
-
- auth_keys_list = []
-
- for k in auth_keys:
- provider = AUTH_PROVIDERS.get(k.provider, None)
-
- if provider is not None:
- name = "%s: %s" % (provider.context.human_name, provider.context.readable_key(k))
- else:
- from forum.authentication.base import ConsumerTemplateContext
- "unknown: %s" % ConsumerTemplateContext.readable_key(k)
-
- auth_keys_list.append({
- 'name': name,
- 'id': k.id
- })
-
- return render_to_response('auth/auth_settings.html', {
- 'form': form,
- 'has_password': user_.has_usable_password(),
- 'auth_keys': auth_keys_list,
- }, context_instance=RequestContext(request))
-
-def newquestion_signin_action(user):
- question = Question.objects.filter(author=user).order_by('-added_at')[0]
- return question.get_absolute_url()
-
-def newanswer_signin_action(user):
- answer = Answer.objects.filter(author=user).order_by('-added_at')[0]
- return answer.get_absolute_url()
-
-POST_SIGNIN_ACTIONS = {
- 'newquestion': newquestion_signin_action,
- 'newanswer': newanswer_signin_action,
-}
-
-def login_and_forward(request, user, forward=None, message=None):
- old_session = request.session.session_key
- user.backend = "django.contrib.auth.backends.ModelBackend"
- login(request, user)
-
- from forum.models import signals#todo: move to auth app
- signals.user_logged_in.send(user=user,session_key=old_session,sender=None)
-
- if not forward:
- signin_action = request.session.get('on_signin_action', None)
- if not signin_action:
- forward = request.session.get('on_signin_url', None)
-
- if not forward:
- forward = reverse('index')
- else:
- try:
- forward = POST_SIGNIN_ACTIONS[signin_action](user)
- except:
- forward = reverse('index')
-
- if message is None:
- message = _("Welcome back %s, you are now logged in") % user.username
-
- request.user.message_set.create(message=message)
- return HttpResponseRedirect(forward)
-
-@login_required
-def signout(request):
- """
- signout from the website. Remove openid from session and kill it.
-
- url : /signout/"
- """
-
- logout(request)
- return HttpResponseRedirect(reverse('index'))
diff --git a/forum_modules/books/__init__.py b/forum_modules/books/__init__.py
deleted file mode 100644
index c51a2bfb..00000000
--- a/forum_modules/books/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-NAME = 'Books'
-DESCRIPTION = "Allows discussion around books."
-CAN_ENABLE = True
diff --git a/forum_modules/books/models.py b/forum_modules/books/models.py
deleted file mode 100644
index a78c0e76..00000000
--- a/forum_modules/books/models.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from django.db import models
-from django.contrib.auth.models import User
-from forum.models import Question
-from django.core.urlresolvers import reverse
-from django.utils.http import urlquote as django_urlquote
-from django.template.defaultfilters import slugify
-
-class Book(models.Model):
- """
- Model for book info
- """
- user = models.ForeignKey(User)
- title = models.CharField(max_length=255)
- short_name = models.CharField(max_length=255)
- author = models.CharField(max_length=255)
- price = models.DecimalField(max_digits=6, decimal_places=2)
- pages = models.SmallIntegerField()
- published_at = models.DateTimeField()
- publication = models.CharField(max_length=255)
- cover_img = models.CharField(max_length=255)
- tagnames = models.CharField(max_length=125)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
- questions = models.ManyToManyField(Question, related_name='book', db_table='book_question')
-
- def get_absolute_url(self):
- return reverse('book', args=[django_urlquote(slugify(self.short_name))])
-
- def __unicode__(self):
- return self.title
-
- class Meta:
- app_label = 'forum'
- db_table = u'book'
-
-class BookAuthorInfo(models.Model):
- """
- Model for book author info
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- blog_url = models.CharField(max_length=255)
- added_at = models.DateTimeField()
- last_edited_at = models.DateTimeField()
-
- class Meta:
- app_label = 'forum'
- db_table = u'book_author_info'
-
-class BookAuthorRss(models.Model):
- """
- Model for book author blog rss
- """
- user = models.ForeignKey(User)
- book = models.ForeignKey(Book)
- title = models.CharField(max_length=255)
- url = models.CharField(max_length=255)
- rss_created_at = models.DateTimeField()
- added_at = models.DateTimeField()
-
- class Meta:
- app_label = 'forum'
- db_table = u'book_author_rss'
\ No newline at end of file
diff --git a/forum_modules/books/urls.py b/forum_modules/books/urls.py
deleted file mode 100644
index bc0811e7..00000000
--- a/forum_modules/books/urls.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.conf.urls.defaults import *
-from django.utils.translation import ugettext as _
-
-import views as app
-
-urlpatterns = patterns('',
- url(r'^%s$' % _('books/'), app.books, name='books'),
- url(r'^%s%s(?P[^/]+)/$' % (_('books/'), _('ask/')), app.ask_book, name='ask_book'),
- url(r'^%s(?P[^/]+)/$' % _('books/'), app.book, name='book'),
-)
\ No newline at end of file
diff --git a/forum_modules/books/views.py b/forum_modules/books/views.py
deleted file mode 100644
index d4907e5f..00000000
--- a/forum_modules/books/views.py
+++ /dev/null
@@ -1,142 +0,0 @@
-from django.shortcuts import render_to_response, get_object_or_404
-from django.http import HttpResponseRedirect, HttpResponse, HttpResponseForbidden, Http404
-from django.template import RequestContext
-from django.contrib.auth.decorators import login_required
-from django.core.urlresolvers import reverse
-from django.utils.html import *
-
-from models import *
-
-from forum.forms import AskForm
-from forum.views.readers import _get_tags_cache_json
-from forum.models import *
-from forum.utils.html import sanitize_html
-
-def books(request):
- return HttpResponseRedirect(reverse('books') + '/mysql-zhaoyang')
-
-def book(request, short_name, unanswered=False):
- """
- 1. questions list
- 2. book info
- 3. author info and blog rss items
- """
- """
- List of Questions, Tagged questions, and Unanswered questions.
- """
- books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
- match_count = len(books)
- if match_count == 0:
- raise Http404
- else:
- # the book info
- book = books[0]
- # get author info
- author_info = BookAuthorInfo.objects.get(book=book)
- # get author rss info
- author_rss = BookAuthorRss.objects.filter(book=book)
-
- # get pagesize from session, if failed then get default value
- user_page_size = request.session.get("page_size", QUESTIONS_PAGE_SIZE)
- # set pagesize equal to logon user specified value in database
- if request.user.is_authenticated() and request.user.questions_per_page > 0:
- user_page_size = request.user.questions_per_page
-
- try:
- page = int(request.GET.get('page', '1'))
- except ValueError:
- page = 1
-
- view_id = request.GET.get('sort', None)
- view_dic = {"latest":"-added_at", "active":"-last_activity_at", "hottest":"-answer_count", "mostvoted":"-score" }
- try:
- orderby = view_dic[view_id]
- except KeyError:
- view_id = "latest"
- orderby = "-added_at"
-
- # check if request is from tagged questions
- if unanswered:
- # check if request is from unanswered questions
- # Article.objects.filter(publications__id__exact=1)
- objects = Question.objects.filter(book__id__exact=book.id, deleted=False, answer_count=0).order_by(orderby)
- else:
- objects = Question.objects.filter(book__id__exact=book.id, deleted=False).order_by(orderby)
-
- # RISK - inner join queries
- objects = objects.select_related();
- objects_list = Paginator(objects, user_page_size)
- questions = objects_list.page(page)
-
- return render_to_response('book.html', {
- "book" : book,
- "author_info" : author_info,
- "author_rss" : author_rss,
- "questions" : questions,
- "context" : {
- 'is_paginated' : True,
- 'pages': objects_list.num_pages,
- 'page': page,
- 'has_previous': questions.has_previous(),
- 'has_next': questions.has_next(),
- 'previous': questions.previous_page_number(),
- 'next': questions.next_page_number(),
- 'base_url' : request.path + '?sort=%s&' % view_id,
- 'page_size' : user_page_size
- }
- }, context_instance=RequestContext(request))
-
-@login_required
-def ask_book(request, short_name):
- if request.method == "POST":
- form = AskForm(request.POST)
- if form.is_valid():
- added_at = datetime.datetime.now()
- html = sanitize_html(markdowner.convert(form.cleaned_data['text']))
- question = Question(
- title = strip_tags(form.cleaned_data['title']),
- author = request.user,
- added_at = added_at,
- last_activity_at = added_at,
- last_activity_by = request.user,
- wiki = form.cleaned_data['wiki'],
- tagnames = form.cleaned_data['tags'].strip(),
- html = html,
- summary = strip_tags(html)[:120]
- )
- if question.wiki:
- question.last_edited_by = question.author
- question.last_edited_at = added_at
- question.wikified_at = added_at
-
- question.save()
-
- # create the first revision
- QuestionRevision.objects.create(
- question = question,
- revision = 1,
- title = question.title,
- author = request.user,
- revised_at = added_at,
- tagnames = question.tagnames,
- summary = CONST['default_version'],
- text = form.cleaned_data['text']
- )
-
- books = Book.objects.extra(where=['short_name = %s'], params=[short_name])
- match_count = len(books)
- if match_count == 1:
- # the book info
- book = books[0]
- book.questions.add(question)
-
- return HttpResponseRedirect(question.get_absolute_url())
- else:
- form = AskForm()
-
- tags = _get_tags_cache_json()
- return render_to_response('ask.html', {
- 'form' : form,
- 'tags' : tags,
- 'email_validation_faq_url': reverse('faq') + '#validate',
- }, context_instance=RequestContext(request))
diff --git a/forum_modules/grapefruit.py b/forum_modules/grapefruit.py
deleted file mode 100644
index ca684745..00000000
--- a/forum_modules/grapefruit.py
+++ /dev/null
@@ -1,1973 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-#
-
-# Copyright (c) 2008, Xavier Basty
-#
-# 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.
-
-'''GrapeFruit - Color manipulation in Python'''
-
-# $Id: grapefruit.py 31 2008-06-15 10:48:06Z xbasty $
-__author__ = 'Xavier Basty '
-__version__ = '0.1a3'
-
-
-# The default white reference, use 2° Standard Observer, D65 (daylight)
-_DEFAULT_WREF = (0.95043, 1.00000, 1.08890)
-
-_oneThird = 1.0 / 3
-_srgbGammaCorrInv = 0.03928 / 12.92
-_sixteenHundredsixteenth = 16.0 / 116
-
-_RybWheel = (
- 0, 26, 52,
- 83, 120, 130,
- 141, 151, 162,
- 177, 190, 204,
- 218, 232, 246,
- 261, 275, 288,
- 303, 317, 330,
- 338, 345, 352,
- 360)
-
-_RgbWheel = (
- 0, 8, 17,
- 26, 34, 41,
- 48, 54, 60,
- 81, 103, 123,
- 138, 155, 171,
- 187, 204, 219,
- 234, 251, 267,
- 282, 298, 329,
- 360)
-
-class Color:
- '''Hold a color value.
-
- Example usage:
-
- To create an instance of the grapefruit.Color from RGB values:
-
- >>> import grapefruit
- >>> r, g, b = 1, 0.5, 0
- >>> col = grapefruit.Color.NewFromRgb(r, g, b)
-
- To get the values of the color in another colorspace:
-
- >>> h, s, v = col.hsv
- >>> l, a, b = col.lab
-
- To get the complementary of a color:
-
- >>> compl = col.ComplementaryColor()
- >>> print compl.hsl
- (210.0, 1.0, 0.5)
-
- To directly convert RGB values to their HSL equivalent:
-
- >>> h, s, l = Color.RgbToHsl(r, g, b)
-
- '''
-
- WHITE_REFERENCE = {
- 'std_A' : (1.09847, 1.00000, 0.35582),
- 'std_B' : (0.99093, 1.00000, 0.85313),
- 'std_C' : (0.98071, 1.00000, 1.18225),
- 'std_D50' : (0.96421, 1.00000, 0.82519),
- 'std_D55' : (0.95680, 1.00000, 0.92148),
- 'std_D65' : (0.95043, 1.00000, 1.08890),
- 'std_D75' : (0.94972, 1.00000, 1.22639),
- 'std_E' : (1.00000, 1.00000, 1.00000),
- 'std_F1' : (0.92834, 1.00000, 1.03665),
- 'std_F2' : (0.99145, 1.00000, 0.67316),
- 'std_F3' : (1.03753, 1.00000, 0.49861),
- 'std_F4' : (1.09147, 1.00000, 0.38813),
- 'std_F5' : (0.90872, 1.00000, 0.98723),
- 'std_F6' : (0.97309, 1.00000, 0.60191),
- 'std_F7' : (0.95017, 1.00000, 1.08630),
- 'std_F8' : (0.96413, 1.00000, 0.82333),
- 'std_F9' : (1.00365, 1.00000, 0.67868),
- 'std_F10' : (0.96174, 1.00000, 0.81712),
- 'std_F11' : (1.00899, 1.00000, 0.64262),
- 'std_F12' : (1.08046, 1.00000, 0.39228),
- 'sup_A' : (1.11142, 1.00000, 0.35200),
- 'sup_B' : (0.99178, 1.00000, 0.84349),
- 'sup_C' : (0.97286, 1.00000, 1.16145),
- 'sup_D50' : (0.96721, 1.00000, 0.81428),
- 'sup_D55' : (0.95797, 1.00000, 0.90925),
- 'sup_D65' : (0.94810, 1.00000, 1.07305),
- 'sup_D75' : (0.94417, 1.00000, 1.20643),
- 'sup_E' : (1.00000, 1.00000, 1.00000),
- 'sup_F1' : (0.94791, 1.00000, 1.03191),
- 'sup_F2' : (1.03245, 1.00000, 0.68990),
- 'sup_F3' : (1.08968, 1.00000, 0.51965),
- 'sup_F4' : (1.14961, 1.00000, 0.40963),
- 'sup_F5' : (0.93369, 1.00000, 0.98636),
- 'sup_F6' : (1.02148, 1.00000, 0.62074),
- 'sup_F7' : (0.95780, 1.00000, 1.07618),
- 'sup_F8' : (0.97115, 1.00000, 0.81135),
- 'sup_F9' : (1.02116, 1.00000, 0.67826),
- 'sup_F10' : (0.99001, 1.00000, 0.83134),
- 'sup_F11' : (1.03820, 1.00000, 0.65555),
- 'sup_F12' : (1.11428, 1.00000, 0.40353)}
-
- NAMED_COLOR = {
- 'aliceblue': '#f0f8ff',
- 'antiquewhite': '#faebd7',
- 'aqua': '#00ffff',
- 'aquamarine': '#7fffd4',
- 'azure': '#f0ffff',
- 'beige': '#f5f5dc',
- 'bisque': '#ffe4c4',
- 'black': '#000000',
- 'blanchedalmond': '#ffebcd',
- 'blue': '#0000ff',
- 'blueviolet': '#8a2be2',
- 'brown': '#a52a2a',
- 'burlywood': '#deb887',
- 'cadetblue': '#5f9ea0',
- 'chartreuse': '#7fff00',
- 'chocolate': '#d2691e',
- 'coral': '#ff7f50',
- 'cornflowerblue': '#6495ed',
- 'cornsilk': '#fff8dc',
- 'crimson': '#dc143c',
- 'cyan': '#00ffff',
- 'darkblue': '#00008b',
- 'darkcyan': '#008b8b',
- 'darkgoldenrod': '#b8860b',
- 'darkgray': '#a9a9a9',
- 'darkgrey': '#a9a9a9',
- 'darkgreen': '#006400',
- 'darkkhaki': '#bdb76b',
- 'darkmagenta': '#8b008b',
- 'darkolivegreen': '#556b2f',
- 'darkorange': '#ff8c00',
- 'darkorchid': '#9932cc',
- 'darkred': '#8b0000',
- 'darksalmon': '#e9967a',
- 'darkseagreen': '#8fbc8f',
- 'darkslateblue': '#483d8b',
- 'darkslategray': '#2f4f4f',
- 'darkslategrey': '#2f4f4f',
- 'darkturquoise': '#00ced1',
- 'darkviolet': '#9400d3',
- 'deeppink': '#ff1493',
- 'deepskyblue': '#00bfff',
- 'dimgray': '#696969',
- 'dimgrey': '#696969',
- 'dodgerblue': '#1e90ff',
- 'firebrick': '#b22222',
- 'floralwhite': '#fffaf0',
- 'forestgreen': '#228b22',
- 'fuchsia': '#ff00ff',
- 'gainsboro': '#dcdcdc',
- 'ghostwhite': '#f8f8ff',
- 'gold': '#ffd700',
- 'goldenrod': '#daa520',
- 'gray': '#808080',
- 'grey': '#808080',
- 'green': '#008000',
- 'greenyellow': '#adff2f',
- 'honeydew': '#f0fff0',
- 'hotpink': '#ff69b4',
- 'indianred': '#cd5c5c',
- 'indigo': '#4b0082',
- 'ivory': '#fffff0',
- 'khaki': '#f0e68c',
- 'lavender': '#e6e6fa',
- 'lavenderblush': '#fff0f5',
- 'lawngreen': '#7cfc00',
- 'lemonchiffon': '#fffacd',
- 'lightblue': '#add8e6',
- 'lightcoral': '#f08080',
- 'lightcyan': '#e0ffff',
- 'lightgoldenrodyellow': '#fafad2',
- 'lightgreen': '#90ee90',
- 'lightgray': '#d3d3d3',
- 'lightgrey': '#d3d3d3',
- 'lightpink': '#ffb6c1',
- 'lightsalmon': '#ffa07a',
- 'lightseagreen': '#20b2aa',
- 'lightskyblue': '#87cefa',
- 'lightslategray': '#778899',
- 'lightslategrey': '#778899',
- 'lightsteelblue': '#b0c4de',
- 'lightyellow': '#ffffe0',
- 'lime': '#00ff00',
- 'limegreen': '#32cd32',
- 'linen': '#faf0e6',
- 'magenta': '#ff00ff',
- 'maroon': '#800000',
- 'mediumaquamarine': '#66cdaa',
- 'mediumblue': '#0000cd',
- 'mediumorchid': '#ba55d3',
- 'mediumpurple': '#9370db',
- 'mediumseagreen': '#3cb371',
- 'mediumslateblue': '#7b68ee',
- 'mediumspringgreen': '#00fa9a',
- 'mediumturquoise': '#48d1cc',
- 'mediumvioletred': '#c71585',
- 'midnightblue': '#191970',
- 'mintcream': '#f5fffa',
- 'mistyrose': '#ffe4e1',
- 'moccasin': '#ffe4b5',
- 'navajowhite': '#ffdead',
- 'navy': '#000080',
- 'oldlace': '#fdf5e6',
- 'olive': '#808000',
- 'olivedrab': '#6b8e23',
- 'orange': '#ffa500',
- 'orangered': '#ff4500',
- 'orchid': '#da70d6',
- 'palegoldenrod': '#eee8aa',
- 'palegreen': '#98fb98',
- 'paleturquoise': '#afeeee',
- 'palevioletred': '#db7093',
- 'papayawhip': '#ffefd5',
- 'peachpuff': '#ffdab9',
- 'peru': '#cd853f',
- 'pink': '#ffc0cb',
- 'plum': '#dda0dd',
- 'powderblue': '#b0e0e6',
- 'purple': '#800080',
- 'red': '#ff0000',
- 'rosybrown': '#bc8f8f',
- 'royalblue': '#4169e1',
- 'saddlebrown': '#8b4513',
- 'salmon': '#fa8072',
- 'sandybrown': '#f4a460',
- 'seagreen': '#2e8b57',
- 'seashell': '#fff5ee',
- 'sienna': '#a0522d',
- 'silver': '#c0c0c0',
- 'skyblue': '#87ceeb',
- 'slateblue': '#6a5acd',
- 'slategray': '#708090',
- 'slategrey': '#708090',
- 'snow': '#fffafa',
- 'springgreen': '#00ff7f',
- 'steelblue': '#4682b4',
- 'tan': '#d2b48c',
- 'teal': '#008080',
- 'thistle': '#d8bfd8',
- 'tomato': '#ff6347',
- 'turquoise': '#40e0d0',
- 'violet': '#ee82ee',
- 'wheat': '#f5deb3',
- 'white': '#ffffff',
- 'whitesmoke': '#f5f5f5',
- 'yellow': '#ffff00',
- 'yellowgreen': '#9acd32'}
-
- def __init__(self, values, mode='rgb', alpha=1.0, wref=_DEFAULT_WREF):
- '''Instantiate a new grapefruit.Color object.
-
- Parameters:
- :values:
- The values of this color, in the specified representation.
- :mode:
- The representation mode used for values.
- :alpha:
- the alpha value (transparency) of this color.
- :wref:
- The whitepoint reference, default is 2° D65.
-
- '''
- if not(isinstance(values, tuple)):
- raise TypeError, 'values must be a tuple'
-
- if mode=='rgb':
- self.__rgb = values
- self.__hsl = Color.RgbToHsl(*values)
- elif mode=='hsl':
- self.__hsl = values
- self.__rgb = Color.HslToRgb(*values)
- else:
- raise ValueError('Invalid color mode: ' + mode)
-
- self.__a = alpha
- self.__wref = wref
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __eq__(self, other):
- try:
- if isinstance(other, Color):
- return (self.__rgb==other.__rgb) and (self.__a==other.__a)
-
- if len(other) != 4:
- return False
- rgba = self.__rgb + (self.__a,)
- return reduce(lambda x, y: x and (y[0]==y[1]), zip(rgba, other), True)
- except TypeError:
- return False
- except AttributeError:
- return False
-
- def __repr__(self):
- return str(self.__rgb + (self.__a,))
-
- def __str__(self):
- '''A string representation of this grapefruit.Color instance.
-
- Returns:
- The RGBA representation of this grapefruit.Color instance.
-
- '''
- return '(%g, %g, %g, %g)' % (self.__rgb + (self.__a,))
-
- def __unicode__(self):
- '''A unicode string representation of this grapefruit.Color instance.
-
- Returns:
- The RGBA representation of this grapefruit.Color instance.
-
- '''
- return u'(%g, %g, %g, %g)' % (self.__rgb + (self.__a,))
-
- def __iter__(self):
- return iter(self.__rgb + (self.__a,))
-
- def __len__(self):
- return 4
-
- @staticmethod
- def RgbToHsl(r, g, b):
- '''Convert the color from RGB coordinates to HSL.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- The color as an (h, s, l) tuple in the range:
- h[0...360],
- s[0...1],
- l[0...1]
-
- >>> Color.RgbToHsl(1, 0.5, 0)
- (30.0, 1.0, 0.5)
-
- '''
- minVal = min(r, g, b) # min RGB value
- maxVal = max(r, g, b) # max RGB value
-
- l = (maxVal + minVal) / 2.0
- if minVal==maxVal:
- return (0.0, 0.0, l) # achromatic (gray)
-
- d = maxVal - minVal # delta RGB value
-
- if l < 0.5: s = d / (maxVal + minVal)
- else: s = d / (2.0 - maxVal - minVal)
-
- dr, dg, db = [(maxVal-val) / d for val in (r, g, b)]
-
- if r==maxVal:
- h = db - dg
- elif g==maxVal:
- h = 2.0 + dr - db
- else:
- h = 4.0 + dg - dr
-
- h = (h*60.0) % 360.0
- return (h, s, l)
-
- @staticmethod
- def _HueToRgb(n1, n2, h):
- h %= 6.0
- if h < 1.0: return n1 + ((n2-n1) * h)
- if h < 3.0: return n2
- if h < 4.0: return n1 + ((n2-n1) * (4.0 - h))
- return n1
-
- @staticmethod
- def HslToRgb(h, s, l):
- '''Convert the color from HSL coordinates to RGB.
-
- Parameters:
- :h:
- The Hue component value [0...1]
- :s:
- The Saturation component value [0...1]
- :l:
- The Lightness component value [0...1]
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- >>> Color.HslToRgb(30.0, 1.0, 0.5)
- (1.0, 0.5, 0.0)
-
- '''
- if s==0: return (l, l, l) # achromatic (gray)
-
- if l<0.5: n2 = l * (1.0 + s)
- else: n2 = l+s - (l*s)
-
- n1 = (2.0 * l) - n2
-
- h /= 60.0
- hueToRgb = Color._HueToRgb
- r = hueToRgb(n1, n2, h + 2)
- g = hueToRgb(n1, n2, h)
- b = hueToRgb(n1, n2, h - 2)
-
- return (r, g, b)
-
- @staticmethod
- def RgbToHsv(r, g, b):
- '''Convert the color from RGB coordinates to HSV.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- The color as an (h, s, v) tuple in the range:
- h[0...360],
- s[0...1],
- v[0...1]
-
- >>> Color.RgbToHsv(1, 0.5, 0)
- (30.0, 1, 1)
-
- '''
- v = max(r, g, b)
- d = v - min(r, g, b)
- if d==0: return (0.0, 0.0, v)
- s = d / v
-
- dr, dg, db = [(v - val) / d for val in (r, g, b)]
-
- if r==v:
- h = db - dg # between yellow & magenta
- elif g==v:
- h = 2.0 + dr - db # between cyan & yellow
- else: # b==v
- h = 4.0 + dg - dr # between magenta & cyan
-
- h = (h*60.0) % 360.0
- return (h, s, v)
-
- @staticmethod
- def HsvToRgb(h, s, v):
- '''Convert the color from RGB coordinates to HSV.
-
- Parameters:
- :h:
- The Hus component value [0...1]
- :s:
- The Saturation component value [0...1]
- :v:
- The Value component [0...1]
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- >>> Color.HslToRgb(30.0, 1.0, 0.5)
- (1.0, 0.5, 0.0)
-
- '''
- if s==0: return (v, v, v) # achromatic (gray)
-
- h /= 60.0
- h = h % 6.0
-
- i = int(h)
- f = h - i
- if not(i&1): f = 1-f # if i is even
-
- m = v * (1.0 - s)
- n = v * (1.0 - (s * f))
-
- if i==0: return (v, n, m)
- if i==1: return (n, v, m)
- if i==2: return (m, v, n)
- if i==3: return (m, n, v)
- if i==4: return (n, m, v)
- return (v, m, n)
-
- @staticmethod
- def RgbToYiq(r, g, b):
- '''Convert the color from RGB to YIQ.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- The color as an (y, i, q) tuple in the range:
- y[0...1],
- i[0...1],
- q[0...1]
-
- >>> '(%g, %g, %g)' % Color.RgbToYiq(1, 0.5, 0)
- '(0.592263, 0.458874, -0.0499818)'
-
- '''
- y = (r * 0.29895808) + (g * 0.58660979) + (b *0.11443213)
- i = (r * 0.59590296) - (g * 0.27405705) - (b *0.32184591)
- q = (r * 0.21133576) - (g * 0.52263517) + (b *0.31129940)
- return (y, i, q)
-
- @staticmethod
- def YiqToRgb(y, i, q):
- '''Convert the color from YIQ coordinates to RGB.
-
- Parameters:
- :y:
- Tte Y component value [0...1]
- :i:
- The I component value [0...1]
- :q:
- The Q component value [0...1]
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- >>> '(%g, %g, %g)' % Color.YiqToRgb(0.592263, 0.458874, -0.0499818)
- '(1, 0.5, 5.442e-007)'
-
- '''
- r = y + (i * 0.9562) + (q * 0.6210)
- g = y - (i * 0.2717) - (q * 0.6485)
- b = y - (i * 1.1053) + (q * 1.7020)
- return (r, g, b)
-
- @staticmethod
- def RgbToYuv(r, g, b):
- '''Convert the color from RGB coordinates to YUV.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- The color as an (y, u, v) tuple in the range:
- y[0...1],
- u[-0.436...0.436],
- v[-0.615...0.615]
-
- >>> '(%g, %g, %g)' % Color.RgbToYuv(1, 0.5, 0)
- '(0.5925, -0.29156, 0.357505)'
-
- '''
- y = (r * 0.29900) + (g * 0.58700) + (b * 0.11400)
- u = -(r * 0.14713) - (g * 0.28886) + (b * 0.43600)
- v = (r * 0.61500) - (g * 0.51499) - (b * 0.10001)
- return (y, u, v)
-
- @staticmethod
- def YuvToRgb(y, u, v):
- '''Convert the color from YUV coordinates to RGB.
-
- Parameters:
- :y:
- The Y component value [0...1]
- :u:
- The U component value [-0.436...0.436]
- :v:
- The V component value [-0.615...0.615]
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- >>> '(%g, %g, %g)' % Color.YuvToRgb(0.5925, -0.2916, 0.3575)
- '(0.999989, 0.500015, -6.3276e-005)'
-
- '''
- r = y + (v * 1.13983)
- g = y - (u * 0.39465) - (v * 0.58060)
- b = y + (u * 2.03211)
- return (r, g, b)
-
- @staticmethod
- def RgbToXyz(r, g, b):
- '''Convert the color from sRGB to CIE XYZ.
-
- The methods assumes that the RGB coordinates are given in the sRGB
- colorspace (D65).
-
- .. note::
-
- Compensation for the sRGB gamma correction is applied before converting.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- The color as an (x, y, z) tuple in the range:
- x[0...1],
- y[0...1],
- z[0...1]
-
- >>> '(%g, %g, %g)' % Color.RgbToXyz(1, 0.5, 0)
- '(0.488941, 0.365682, 0.0448137)'
-
- '''
- r, g, b = [((v <= 0.03928) and [v / 12.92] or [((v+0.055) / 1.055) **2.4])[0] for v in (r, g, b)]
-
- x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805)
- y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722)
- z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505)
- return (x, y, z)
-
- @staticmethod
- def XyzToRgb(x, y, z):
- '''Convert the color from CIE XYZ coordinates to sRGB.
-
- .. note::
-
- Compensation for sRGB gamma correction is applied before converting.
-
- Parameters:
- :x:
- The X component value [0...1]
- :y:
- The Y component value [0...1]
- :z:
- The Z component value [0...1]
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- >>> '(%g, %g, %g)' % Color.XyzToRgb(0.488941, 0.365682, 0.0448137)
- '(1, 0.5, 6.81883e-008)'
-
- '''
- r = (x * 3.2406255) - (y * 1.5372080) - (z * 0.4986286)
- g = -(x * 0.9689307) + (y * 1.8757561) + (z * 0.0415175)
- b = (x * 0.0557101) - (y * 0.2040211) + (z * 1.0569959)
- return tuple((((v <= _srgbGammaCorrInv) and [v * 12.92] or [(1.055 * (v ** (1/2.4))) - 0.055])[0] for v in (r, g, b)))
-
- @staticmethod
- def XyzToLab(x, y, z, wref=_DEFAULT_WREF):
- '''Convert the color from CIE XYZ to CIE L*a*b*.
-
- Parameters:
- :x:
- The X component value [0...1]
- :y:
- The Y component value [0...1]
- :z:
- The Z component value [0...1]
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- The color as an (L, a, b) tuple in the range:
- L[0...100],
- a[-1...1],
- b[-1...1]
-
- >>> '(%g, %g, %g)' % Color.XyzToLab(0.488941, 0.365682, 0.0448137)
- '(66.9518, 0.43084, 0.739692)'
-
- >>> '(%g, %g, %g)' % Color.XyzToLab(0.488941, 0.365682, 0.0448137, Color.WHITE_REFERENCE['std_D50'])
- '(66.9518, 0.411663, 0.67282)'
-
- '''
- # White point correction
- x /= wref[0]
- y /= wref[1]
- z /= wref[2]
-
- # Nonlinear distortion and linear transformation
- x, y, z = [((v > 0.008856) and [v**_oneThird] or [(7.787 * v) + _sixteenHundredsixteenth])[0] for v in (x, y, z)]
-
- # Vector scaling
- l = (116 * y) - 16
- a = 5.0 * (x - y)
- b = 2.0 * (y - z)
-
- return (l, a, b)
-
- @staticmethod
- def LabToXyz(l, a, b, wref=_DEFAULT_WREF):
- '''Convert the color from CIE L*a*b* to CIE 1931 XYZ.
-
- Parameters:
- :l:
- The L component [0...100]
- :a:
- The a component [-1...1]
- :b:
- The a component [-1...1]
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- The color as an (x, y, z) tuple in the range:
- x[0...q],
- y[0...1],
- z[0...1]
-
- >>> '(%g, %g, %g)' % Color.LabToXyz(66.9518, 0.43084, 0.739692)
- '(0.488941, 0.365682, 0.0448137)'
-
- >>> '(%g, %g, %g)' % Color.LabToXyz(66.9518, 0.411663, 0.67282, Color.WHITE_REFERENCE['std_D50'])
- '(0.488941, 0.365682, 0.0448138)'
-
- '''
- y = (l + 16) / 116
- x = (a / 5.0) + y
- z = y - (b / 2.0)
- return tuple((((v > 0.206893) and [v**3] or [(v - _sixteenHundredsixteenth) / 7.787])[0] * w for v, w in zip((x, y, z), wref)))
-
- @staticmethod
- def CmykToCmy(c, m, y, k):
- '''Convert the color from CMYK coordinates to CMY.
-
- Parameters:
- :c:
- The Cyan component value [0...1]
- :m:
- The Magenta component value [0...1]
- :y:
- The Yellow component value [0...1]
- :k:
- The Black component value [0...1]
-
- Returns:
- The color as an (c, m, y) tuple in the range:
- c[0...1],
- m[0...1],
- y[0...1]
-
- >>> '(%g, %g, %g)' % Color.CmykToCmy(1, 0.32, 0, 0.5)
- '(1, 0.66, 0.5)'
-
- '''
- mk = 1-k
- return ((c*mk + k), (m*mk + k), (y*mk + k))
-
- @staticmethod
- def CmyToCmyk(c, m, y):
- '''Convert the color from CMY coordinates to CMYK.
-
- Parameters:
- :c:
- The Cyan component value [0...1]
- :m:
- The Magenta component value [0...1]
- :y:
- The Yellow component value [0...1]
-
- Returns:
- The color as an (c, m, y, k) tuple in the range:
- c[0...1],
- m[0...1],
- y[0...1],
- k[0...1]
-
- >>> '(%g, %g, %g, %g)' % Color.CmyToCmyk(1, 0.66, 0.5)
- '(1, 0.32, 0, 0.5)'
-
- '''
- k = min(c, m, y)
- if k==1.0: return (0.0, 0.0, 0.0, 1.0)
- mk = 1-k
- return ((c-k) / mk, (m-k) / mk, (y-k) / mk, k)
-
- @staticmethod
- def RgbToCmy(r, g, b):
- '''Convert the color from RGB coordinates to CMY.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- The color as an (c, m, y) tuple in the range:
- c[0...1],
- m[0...1],
- y[0...1]
-
- >>> Color.RgbToCmy(1, 0.5, 0)
- (0, 0.5, 1)
-
- '''
- return (1-r, 1-g, 1-b)
-
- @staticmethod
- def CmyToRgb(c, m, y):
- '''Convert the color from CMY coordinates to RGB.
-
- Parameters:
- :c:
- The Cyan component value [0...1]
- :m:
- The Magenta component value [0...1]
- :y:
- The Yellow component value [0...1]
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- >>> Color.CmyToRgb(0, 0.5, 1)
- (1, 0.5, 0)
-
- '''
- return (1-c, 1-m, 1-y)
-
- @staticmethod
- def RgbToHtml(r, g, b):
- '''Convert the color from (r, g, b) to #RRGGBB.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- A CSS string representation of this color (#RRGGBB).
-
- >>> Color.RgbToHtml(1, 0.5, 0)
- '#ff8000'
-
- '''
- return '#%02x%02x%02x' % tuple((min(round(v*255), 255) for v in (r, g, b)))
-
- @staticmethod
- def HtmlToRgb(html):
- '''Convert the HTML color to (r, g, b).
-
- Parameters:
- :html:
- the HTML definition of the color (#RRGGBB or #RGB or a color name).
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- Throws:
- :ValueError:
- If html is neither a known color name or a hexadecimal RGB
- representation.
-
- >>> '(%g, %g, %g)' % Color.HtmlToRgb('#ff8000')
- '(1, 0.501961, 0)'
- >>> '(%g, %g, %g)' % Color.HtmlToRgb('ff8000')
- '(1, 0.501961, 0)'
- >>> '(%g, %g, %g)' % Color.HtmlToRgb('#f60')
- '(1, 0.4, 0)'
- >>> '(%g, %g, %g)' % Color.HtmlToRgb('f60')
- '(1, 0.4, 0)'
- >>> '(%g, %g, %g)' % Color.HtmlToRgb('lemonchiffon')
- '(1, 0.980392, 0.803922)'
-
- '''
- html = html.strip().lower()
- if html[0]=='#':
- html = html[1:]
- elif Color.NAMED_COLOR.has_key(html):
- html = Color.NAMED_COLOR[html][1:]
-
- if len(html)==6:
- rgb = html[:2], html[2:4], html[4:]
- elif len(html)==3:
- rgb = ['%c%c' % (v,v) for v in html]
- else:
- raise ValueError, 'input #%s is not in #RRGGBB format' % html
-
- return tuple(((int(n, 16) / 255.0) for n in rgb))
-
- @staticmethod
- def RgbToPil(r, g, b):
- '''Convert the color from RGB to a PIL-compatible integer.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- A PIL compatible integer (0xBBGGRR).
-
- >>> '0x%06x' % Color.RgbToPil(1, 0.5, 0)
- '0x0080ff'
-
- '''
- r, g, b = [min(int(round(v*255)), 255) for v in (r, g, b)]
- return (b << 16) + (g << 8) + r
-
- @staticmethod
- def PilToRgb(pil):
- '''Convert the color from a PIL-compatible integer to RGB.
-
- Parameters:
- pil: a PIL compatible color representation (0xBBGGRR)
- Returns:
- The color as an (r, g, b) tuple in the range:
- the range:
- r: [0...1]
- g: [0...1]
- b: [0...1]
-
- >>> '(%g, %g, %g)' % Color.PilToRgb(0x0080ff)
- '(1, 0.501961, 0)'
-
- '''
- r = 0xff & pil
- g = 0xff & (pil >> 8)
- b = 0xff & (pil >> 16)
- return tuple((v / 255.0 for v in (r, g, b)))
-
- @staticmethod
- def _WebSafeComponent(c, alt=False):
- '''Convert a color component to its web safe equivalent.
-
- Parameters:
- :c:
- The component value [0...1]
- :alt:
- If True, return the alternative value instead of the nearest one.
-
- Returns:
- The web safe equivalent of the component value.
-
- '''
- # This sucks, but floating point between 0 and 1 is quite fuzzy...
- # So we just change the scale a while to make the equality tests
- # work, otherwise it gets wrong at some decimal far to the right.
- sc = c * 100.0
-
- # If the color is already safe, return it straight away
- d = sc % 20
- if d==0: return c
-
- # Get the lower and upper safe values
- l = sc - d
- u = l + 20
-
- # Return the 'closest' value according to the alt flag
- if alt:
- if (sc-l) >= (u-sc): return l/100.0
- else: return u/100.0
- else:
- if (sc-l) >= (u-sc): return u/100.0
- else: return l/100.0
-
- @staticmethod
- def RgbToWebSafe(r, g, b, alt=False):
- '''Convert the color from RGB to 'web safe' RGB
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
- :alt:
- If True, use the alternative color instead of the nearest one.
- Can be used for dithering.
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- >>> '(%g, %g, %g)' % Color.RgbToWebSafe(1, 0.55, 0.0)
- '(1, 0.6, 0)'
-
- '''
- webSafeComponent = Color._WebSafeComponent
- return tuple((webSafeComponent(v, alt) for v in (r, g, b)))
-
- @staticmethod
- def RgbToGreyscale(r, g, b):
- '''Convert the color from RGB to its greyscale equivalent
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
-
- Returns:
- The color as an (r, g, b) tuple in the range:
- the range:
- r[0...1],
- g[0...1],
- b[0...1]
-
- >>> '(%g, %g, %g)' % Color.RgbToGreyscale(1, 0.8, 0)
- '(0.6, 0.6, 0.6)'
-
- '''
- v = (r + g + b) / 3.0
- return (v, v, v)
-
- @staticmethod
- def RgbToRyb(hue):
- '''Maps a hue on the RGB color wheel to Itten's RYB wheel.
-
- Parameters:
- :hue:
- The hue on the RGB color wheel [0...360]
-
- Returns:
- An approximation of the corresponding hue on Itten's RYB wheel.
-
- >>> Color.RgbToRyb(15)
- 26
-
- '''
- d = hue % 15
- i = int(hue / 15)
- x0 = _RybWheel[i]
- x1 = _RybWheel[i+1]
- return x0 + (x1-x0) * d / 15
-
- @staticmethod
- def RybToRgb(hue):
- '''Maps a hue on Itten's RYB color wheel to the standard RGB wheel.
-
- Parameters:
- :hue:
- The hue on Itten's RYB color wheel [0...360]
-
- Returns:
- An approximation of the corresponding hue on the standard RGB wheel.
-
- >>> Color.RybToRgb(15)
- 8
-
- '''
- d = hue % 15
- i = int(hue / 15)
- x0 = _RgbWheel[i]
- x1 = _RgbWheel[i+1]
- return x0 + (x1-x0) * d / 15
-
- @staticmethod
- def NewFromRgb(r, g, b, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed RGB values.
-
- Parameters:
- :r:
- The Red component value [0...1]
- :g:
- The Green component value [0...1]
- :b:
- The Blue component value [0...1]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromRgb(1.0, 0.5, 0.0)
- (1.0, 0.5, 0.0, 1.0)
- >>> Color.NewFromRgb(1.0, 0.5, 0.0, 0.5)
- (1.0, 0.5, 0.0, 0.5)
-
- '''
- return Color((r, g, b), 'rgb', alpha, wref)
-
- @staticmethod
- def NewFromHsl(h, s, l, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed HSL values.
-
- Parameters:
- :h:
- The Hue component value [0...1]
- :s:
- The Saturation component value [0...1]
- :l:
- The Lightness component value [0...1]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 1, 0.5)
- (1.0, 0.5, 0.0, 1.0)
- >>> Color.NewFromHsl(30, 1, 0.5, 0.5)
- (1.0, 0.5, 0.0, 0.5)
-
- '''
- return Color((h, s, l), 'hsl', alpha, wref)
-
- @staticmethod
- def NewFromHsv(h, s, v, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed HSV values.
-
- Parameters:
- :h:
- The Hus component value [0...1]
- :s:
- The Saturation component value [0...1]
- :v:
- The Value component [0...1]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsv(30, 1, 1)
- (1.0, 0.5, 0.0, 1.0)
- >>> Color.NewFromHsv(30, 1, 1, 0.5)
- (1.0, 0.5, 0.0, 0.5)
-
- '''
- h2, s, l = Color.RgbToHsl(*Color.HsvToRgb(h, s, v))
- return Color((h, s, l), 'hsl', alpha, wref)
-
- @staticmethod
- def NewFromYiq(y, i, q, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed YIQ values.
-
- Parameters:
- :y:
- The Y component value [0...1]
- :i:
- The I component value [0...1]
- :q:
- The Q component value [0...1]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> str(Color.NewFromYiq(0.5922, 0.45885,-0.05))
- '(0.999902, 0.499955, -6.6905e-005, 1)'
- >>> str(Color.NewFromYiq(0.5922, 0.45885,-0.05, 0.5))
- '(0.999902, 0.499955, -6.6905e-005, 0.5)'
-
- '''
- return Color(Color.YiqToRgb(y, i, q), 'rgb', alpha, wref)
-
- @staticmethod
- def NewFromYuv(y, u, v, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed YUV values.
-
- Parameters:
- :y:
- The Y component value [0...1]
- :u:
- The U component value [-0.436...0.436]
- :v:
- The V component value [-0.615...0.615]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> str(Color.NewFromYuv(0.5925, -0.2916, 0.3575))
- '(0.999989, 0.500015, -6.3276e-005, 1)'
- >>> str(Color.NewFromYuv(0.5925, -0.2916, 0.3575, 0.5))
- '(0.999989, 0.500015, -6.3276e-005, 0.5)'
-
- '''
- return Color(Color.YuvToRgb(y, u, v), 'rgb', alpha, wref)
-
- @staticmethod
- def NewFromXyz(x, y, z, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed CIE-XYZ values.
-
- Parameters:
- :x:
- The Red component value [0...1]
- :y:
- The Green component value [0...1]
- :z:
- The Blue component value [0...1]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> str(Color.NewFromXyz(0.488941, 0.365682, 0.0448137))
- '(1, 0.5, 6.81883e-008, 1)'
- >>> str(Color.NewFromXyz(0.488941, 0.365682, 0.0448137, 0.5))
- '(1, 0.5, 6.81883e-008, 0.5)'
-
- '''
- return Color(Color.XyzToRgb(x, y, z), 'rgb', alpha, wref)
-
- @staticmethod
- def NewFromLab(l, a, b, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed CIE-LAB values.
-
- Parameters:
- :l:
- The L component [0...100]
- :a:
- The a component [-1...1]
- :b:
- The a component [-1...1]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692))
- '(1, 0.5, 1.09491e-008, 1)'
- >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, wref=Color.WHITE_REFERENCE['std_D50']))
- '(1.01238, 0.492011, -0.14311, 1)'
- >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, 0.5))
- '(1, 0.5, 1.09491e-008, 0.5)'
- >>> str(Color.NewFromLab(66.9518, 0.43084, 0.739692, 0.5, Color.WHITE_REFERENCE['std_D50']))
- '(1.01238, 0.492011, -0.14311, 0.5)'
-
- '''
- return Color(Color.XyzToRgb(*Color.LabToXyz(l, a, b, wref)), 'rgb', alpha, wref)
-
- @staticmethod
- def NewFromCmy(c, m, y, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed CMY values.
-
- Parameters:
- :c:
- The Cyan component value [0...1]
- :m:
- The Magenta component value [0...1]
- :y:
- The Yellow component value [0...1]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromCmy(0, 0.5, 1)
- (1, 0.5, 0, 1.0)
- >>> Color.NewFromCmy(0, 0.5, 1, 0.5)
- (1, 0.5, 0, 0.5)
-
- '''
- return Color(Color.CmyToRgb(c, m, y), 'rgb', alpha, wref)
-
- @staticmethod
- def NewFromCmyk(c, m, y, k, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed CMYK values.
-
- Parameters:
- :c:
- The Cyan component value [0...1]
- :m:
- The Magenta component value [0...1]
- :y:
- The Yellow component value [0...1]
- :k:
- The Black component value [0...1]
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> str(Color.NewFromCmyk(1, 0.32, 0, 0.5))
- '(0, 0.34, 0.5, 1)'
- >>> str(Color.NewFromCmyk(1, 0.32, 0, 0.5, 0.5))
- '(0, 0.34, 0.5, 0.5)'
-
- '''
- return Color(Color.CmyToRgb(*Color.CmykToCmy(c, m, y, k)), 'rgb', alpha, wref)
-
- @staticmethod
- def NewFromHtml(html, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed HTML color definition.
-
- Parameters:
- :html:
- The HTML definition of the color (#RRGGBB or #RGB or a color name).
- :alpha:
- The color transparency [0...1], default is opaque.
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> str(Color.NewFromHtml('#ff8000'))
- '(1, 0.501961, 0, 1)'
- >>> str(Color.NewFromHtml('ff8000'))
- '(1, 0.501961, 0, 1)'
- >>> str(Color.NewFromHtml('#f60'))
- '(1, 0.4, 0, 1)'
- >>> str(Color.NewFromHtml('f60'))
- '(1, 0.4, 0, 1)'
- >>> str(Color.NewFromHtml('lemonchiffon'))
- '(1, 0.980392, 0.803922, 1)'
- >>> str(Color.NewFromHtml('#ff8000', 0.5))
- '(1, 0.501961, 0, 0.5)'
-
- '''
- return Color(Color.HtmlToRgb(html), 'rgb', alpha, wref)
-
- @staticmethod
- def NewFromPil(pil, alpha=1.0, wref=_DEFAULT_WREF):
- '''Create a new instance based on the specifed PIL color.
-
- Parameters:
- :pil:
- A PIL compatible color representation (0xBBGGRR)
- :alpha:
- The color transparency [0...1], default is opaque
- :wref:
- The whitepoint reference, default is 2° D65.
-
- Returns:
- A grapefruit.Color instance.
-
- >>> str(Color.NewFromPil(0x0080ff))
- '(1, 0.501961, 0, 1)'
- >>> str(Color.NewFromPil(0x0080ff, 0.5))
- '(1, 0.501961, 0, 0.5)'
-
- '''
- return Color(Color.PilToRgb(pil), 'rgb', alpha, wref)
-
- def __GetAlpha(self):
- return self.__a
- alpha = property(fget=__GetAlpha, doc='The transparency of this color. 0.0 is transparent and 1.0 is fully opaque.')
-
- def __GetWRef(self):
- return self.__wref
- whiteRef = property(fget=__GetWRef, doc='the white reference point of this color.')
-
- def __GetRGB(self):
- return self.__rgb
- rgb = property(fget=__GetRGB, doc='The RGB values of this Color.')
-
- def __GetHue(self):
- return self.__hsl[0]
- hue = property(fget=__GetHue, doc='The hue of this color.')
-
- def __GetHSL(self):
- return self.__hsl
- hsl = property(fget=__GetHSL, doc='The HSL values of this Color.')
-
- def __GetHSV(self):
- h, s, v = Color.RgbToHsv(*self.__rgb)
- return (self.__hsl[0], s, v)
- hsv = property(fget=__GetHSV, doc='The HSV values of this Color.')
-
- def __GetYIQ(self):
- return Color.RgbToYiq(*self.__rgb)
- yiq = property(fget=__GetYIQ, doc='The YIQ values of this Color.')
-
- def __GetYUV(self):
- return Color.RgbToYuv(*self.__rgb)
- yuv = property(fget=__GetYUV, doc='The YUV values of this Color.')
-
- def __GetXYZ(self):
- return Color.RgbToXyz(*self.__rgb)
- xyz = property(fget=__GetXYZ, doc='The CIE-XYZ values of this Color.')
-
- def __GetLAB(self):
- return Color.XyzToLab(wref=self.__wref, *Color.RgbToXyz(*self.__rgb))
- lab = property(fget=__GetLAB, doc='The CIE-LAB values of this Color.')
-
- def __GetCMY(self):
- return Color.RgbToCmy(*self.__rgb)
- cmy = property(fget=__GetCMY, doc='The CMY values of this Color.')
-
- def __GetCMYK(self):
- return Color.CmyToCmyk(*Color.RgbToCmy(*self.__rgb))
- cmyk = property(fget=__GetCMYK, doc='The CMYK values of this Color.')
-
- def __GetHTML(self):
- return Color.RgbToHtml(*self.__rgb)
- html = property(fget=__GetHTML, doc='This Color as an HTML color definition.')
-
- def __GetPIL(self):
- return Color.RgbToPil(*self.__rgb)
- pil = property(fget=__GetPIL, doc='This Color as a PIL compatible value.')
-
- def __GetwebSafe(self):
- return Color.RgbToWebSafe(*self.__rgb)
- webSafe = property(fget=__GetwebSafe, doc='The web safe color nearest to this one (RGB).')
-
- def __GetGreyscale(self):
- return Color.RgbToGreyscale(*self.rgb)
- greyscale = property(fget=__GetGreyscale, doc='The greyscale equivalent to this color (RGB).')
-
- def ColorWithAlpha(self, alpha):
- '''Create a new instance based on this one with a new alpha value.
-
- Parameters:
- :alpha:
- The transparency of the new color [0...1].
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromRgb(1.0, 0.5, 0.0, 1.0).ColorWithAlpha(0.5)
- (1.0, 0.5, 0.0, 0.5)
-
- '''
- return Color(self.__rgb, 'rgb', alpha, self.__wref)
-
- def ColorWithWhiteRef(self, wref, labAsRef=False):
- '''Create a new instance based on this one with a new white reference.
-
- Parameters:
- :wref:
- The whitepoint reference.
- :labAsRef:
- If True, the L*a*b* values of the current instance are used as reference
- for the new color; otherwise, the RGB values are used as reference.
-
- Returns:
- A grapefruit.Color instance.
-
-
- >>> c = Color.NewFromRgb(1.0, 0.5, 0.0, 1.0, Color.WHITE_REFERENCE['std_D65'])
-
- >>> c2 = c.ColorWithWhiteRef(Color.WHITE_REFERENCE['sup_D50'])
- >>> c2.rgb
- (1.0, 0.5, 0.0)
- >>> '(%g, %g, %g)' % c2.whiteRef
- '(0.96721, 1, 0.81428)'
-
- >>> c2 = c.ColorWithWhiteRef(Color.WHITE_REFERENCE['sup_D50'], labAsRef=True)
- >>> '(%g, %g, %g)' % c2.rgb
- '(1.01463, 0.490339, -0.148131)'
- >>> '(%g, %g, %g)' % c2.whiteRef
- '(0.96721, 1, 0.81428)'
- >>> '(%g, %g, %g)' % c.lab
- '(66.9518, 0.43084, 0.739692)'
- >>> '(%g, %g, %g)' % c2.lab
- '(66.9518, 0.43084, 0.739693)'
-
- '''
- if labAsRef:
- l, a, b = self.__GetLAB()
- return Color.NewFromLab(l, a, b, self.__a, wref)
- else:
- return Color(self.__rgb, 'rgb', self.__a, wref)
-
- def ColorWithHue(self, hue):
- '''Create a new instance based on this one with a new hue.
-
- Parameters:
- :hue:
- The hue of the new color [0...360].
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 1, 0.5).ColorWithHue(60)
- (1.0, 1.0, 0.0, 1.0)
- >>> Color.NewFromHsl(30, 1, 0.5).ColorWithHue(60).hsl
- (60, 1, 0.5)
-
- '''
- h, s, l = self.__hsl
- return Color((hue, s, l), 'hsl', self.__a, self.__wref)
-
- def ColorWithSaturation(self, saturation):
- '''Create a new instance based on this one with a new saturation value.
-
- .. note::
-
- The saturation is defined for the HSL mode.
-
- Parameters:
- :saturation:
- The saturation of the new color [0...1].
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 1, 0.5).ColorWithSaturation(0.5)
- (0.75, 0.5, 0.25, 1.0)
- >>> Color.NewFromHsl(30, 1, 0.5).ColorWithSaturation(0.5).hsl
- (30, 0.5, 0.5)
-
- '''
- h, s, l = self.__hsl
- return Color((h, saturation, l), 'hsl', self.__a, self.__wref)
-
- def ColorWithLightness(self, lightness):
- '''Create a new instance based on this one with a new lightness value.
-
- Parameters:
- :lightness:
- The lightness of the new color [0...1].
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 1, 0.5).ColorWithLightness(0.25)
- (0.5, 0.25, 0.0, 1.0)
- >>> Color.NewFromHsl(30, 1, 0.5).ColorWithLightness(0.25).hsl
- (30, 1, 0.25)
-
- '''
- h, s, l = self.__hsl
- return Color((h, s, lightness), 'hsl', self.__a, self.__wref)
-
- def DarkerColor(self, level):
- '''Create a new instance based on this one but darker.
-
- Parameters:
- :level:
- The amount by which the color should be darkened to produce
- the new one [0...1].
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 1, 0.5).DarkerColor(0.25)
- (0.5, 0.25, 0.0, 1.0)
- >>> Color.NewFromHsl(30, 1, 0.5).DarkerColor(0.25).hsl
- (30, 1, 0.25)
-
- '''
- h, s, l = self.__hsl
- return Color((h, s, max(l - level, 0)), 'hsl', self.__a, self.__wref)
-
- def LighterColor(self, level):
- '''Create a new instance based on this one but lighter.
-
- Parameters:
- :level:
- The amount by which the color should be lightened to produce
- the new one [0...1].
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 1, 0.5).LighterColor(0.25)
- (1.0, 0.75, 0.5, 1.0)
- >>> Color.NewFromHsl(30, 1, 0.5).LighterColor(0.25).hsl
- (30, 1, 0.75)
-
- '''
- h, s, l = self.__hsl
- return Color((h, s, min(l + level, 1)), 'hsl', self.__a, self.__wref)
-
- def Saturate(self, level):
- '''Create a new instance based on this one but more saturated.
-
- Parameters:
- :level:
- The amount by which the color should be saturated to produce
- the new one [0...1].
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 0.5, 0.5).Saturate(0.25)
- (0.875, 0.5, 0.125, 1.0)
- >>> Color.NewFromHsl(30, 0.5, 0.5).Saturate(0.25).hsl
- (30, 0.75, 0.5)
-
- '''
- h, s, l = self.__hsl
- return Color((h, min(s + level, 1), l), 'hsl', self.__a, self.__wref)
-
- def Desaturate(self, level):
- '''Create a new instance based on this one but less saturated.
-
- Parameters:
- :level:
- The amount by which the color should be desaturated to produce
- the new one [0...1].
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 0.5, 0.5).Desaturate(0.25)
- (0.625, 0.5, 0.375, 1.0)
- >>> Color.NewFromHsl(30, 0.5, 0.5).Desaturate(0.25).hsl
- (30, 0.25, 0.5)
-
- '''
- h, s, l = self.__hsl
- return Color((h, max(s - level, 0), l), 'hsl', self.__a, self.__wref)
-
- def WebSafeDither(self):
- '''Return the two websafe colors nearest to this one.
-
- Returns:
- A tuple of two grapefruit.Color instances which are the two
- web safe colors closest this one.
-
- >>> c = Color.NewFromRgb(1.0, 0.45, 0.0)
- >>> c1, c2 = c.WebSafeDither()
- >>> str(c1)
- '(1, 0.4, 0, 1)'
- >>> str(c2)
- '(1, 0.6, 0, 1)'
-
- '''
- return (
- Color(Color.RgbToWebSafe(*self.__rgb), 'rgb', self.__a, self.__wref),
- Color(Color.RgbToWebSafe(alt=True, *self.__rgb), 'rgb', self.__a, self.__wref))
-
- def Gradient(self, target, steps=100):
- '''Create a list with the gradient colors between this and the other color.
-
- Parameters:
- :target:
- The grapefruit.Color at the other end of the gradient.
- :steps:
- The number of gradients steps to create.
-
-
- Returns:
- A list of grapefruit.Color instances.
-
- >>> c1 = Color.NewFromRgb(1.0, 0.0, 0.0, alpha=1)
- >>> c2 = Color.NewFromRgb(0.0, 1.0, 0.0, alpha=0)
- >>> c1.Gradient(c2, 3)
- [(0.75, 0.25, 0.0, 0.75), (0.5, 0.5, 0.0, 0.5), (0.25, 0.75, 0.0, 0.25)]
-
- '''
- gradient = []
- rgba1 = self.__rgb + (self.__a,)
- rgba2 = target.__rgb + (target.__a,)
-
- steps += 1
- for n in xrange(1, steps):
- d = 1.0*n/steps
- r = (rgba1[0]*(1-d)) + (rgba2[0]*d)
- g = (rgba1[1]*(1-d)) + (rgba2[1]*d)
- b = (rgba1[2]*(1-d)) + (rgba2[2]*d)
- a = (rgba1[3]*(1-d)) + (rgba2[3]*d)
-
- gradient.append(Color((r, g, b), 'rgb', a, self.__wref))
-
- return gradient
-
- def ComplementaryColor(self, mode='ryb'):
- '''Create a new instance which is the complementary color of this one.
-
- Parameters:
- :mode:
- Select which color wheel to use for the generation (ryb/rgb).
-
-
- Returns:
- A grapefruit.Color instance.
-
- >>> Color.NewFromHsl(30, 1, 0.5).ComplementaryColor()
- (0.0, 0.5, 1.0, 1.0)
- >>> Color.NewFromHsl(30, 1, 0.5).ComplementaryColor().hsl
- (210, 1, 0.5)
-
- '''
- h, s, l = self.__hsl
-
- if mode == 'ryb': h = Color.RgbToRyb(h)
- h = (h+180)%360
- if mode == 'ryb': h = Color.RybToRgb(h)
-
- return Color((h, s, l), 'hsl', self.__a, self.__wref)
-
- def MonochromeScheme(self):
- '''Return 4 colors in the same hue with varying saturation/lightness.
-
- Returns:
- A tuple of 4 grapefruit.Color in the same hue as this one,
- with varying saturation/lightness.
-
- >>> c = Color.NewFromHsl(30, 0.5, 0.5)
- >>> ['(%g, %g, %g)' % clr.hsl for clr in c.MonochromeScheme()]
- ['(30, 0.2, 0.8)', '(30, 0.5, 0.3)', '(30, 0.2, 0.6)', '(30, 0.5, 0.8)']
-
- '''
- def _wrap(x, min, thres, plus):
- if (x-min) < thres: return x + plus
- else: return x-min
-
- h, s, l = self.__hsl
-
- s1 = _wrap(s, 0.3, 0.1, 0.3)
- l1 = _wrap(l, 0.5, 0.2, 0.3)
-
- s2 = s
- l2 = _wrap(l, 0.2, 0.2, 0.6)
-
- s3 = s1
- l3 = max(0.2, l + (1-l)*0.2)
-
- s4 = s
- l4 = _wrap(l, 0.5, 0.2, 0.3)
-
- return (
- Color((h, s1, l1), 'hsl', self.__a, self.__wref),
- Color((h, s2, l2), 'hsl', self.__a, self.__wref),
- Color((h, s3, l3), 'hsl', self.__a, self.__wref),
- Color((h, s4, l4), 'hsl', self.__a, self.__wref))
-
- def TriadicScheme(self, angle=120, mode='ryb'):
- '''Return two colors forming a triad or a split complementary with this one.
-
- Parameters:
- :angle:
- The angle between the hues of the created colors.
- The default value makes a triad.
- :mode:
- Select which color wheel to use for the generation (ryb/rgb).
-
- Returns:
- A tuple of two grapefruit.Color forming a color triad with
- this one or a split complementary.
-
- >>> c1 = Color.NewFromHsl(30, 1, 0.5)
-
- >>> c2, c3 = c1.TriadicScheme()
- >>> c2.hsl
- (150.0, 1, 0.5)
- >>> c3.hsl
- (270.0, 1, 0.5)
-
- >>> c2, c3 = c1.TriadicScheme(40)
- >>> c2.hsl
- (190.0, 1, 0.5)
- >>> c3.hsl
- (230.0, 1, 0.5)
-
- '''
- h, s, l = self.__hsl
- angle = min(angle, 120) / 2.0
-
- if mode == 'ryb': h = Color.RgbToRyb(h)
- h += 180
- h1 = (h - angle) % 360
- h2 = (h + angle) % 360
- if mode == 'ryb':
- h1 = Color.RybToRgb(h1)
- h2 = Color.RybToRgb(h2)
-
- return (
- Color((h1, s, l), 'hsl', self.__a, self.__wref),
- Color((h2, s, l), 'hsl', self.__a, self.__wref))
-
- def TetradicScheme(self, angle=30, mode='ryb'):
- '''Return three colors froming a tetrad with this one.
-
- Parameters:
- :angle:
- The angle to substract from the adjacent colors hues [-90...90].
- You can use an angle of zero to generate a square tetrad.
- :mode:
- Select which color wheel to use for the generation (ryb/rgb).
-
- Returns:
- A tuple of three grapefruit.Color forming a color tetrad with
- this one.
-
- >>> col = Color.NewFromHsl(30, 1, 0.5)
- >>> [c.hsl for c in col.TetradicScheme(mode='rgb', angle=30)]
- [(90, 1, 0.5), (210, 1, 0.5), (270, 1, 0.5)]
-
- '''
- h, s, l = self.__hsl
-
- if mode == 'ryb': h = Color.RgbToRyb(h)
- h1 = (h + 90 - angle) % 360
- h2 = (h + 180) % 360
- h3 = (h + 270 - angle) % 360
- if mode == 'ryb':
- h1 = Color.RybToRgb(h1)
- h2 = Color.RybToRgb(h2)
- h3 = Color.RybToRgb(h3)
-
- return (
- Color((h1, s, l), 'hsl', self.__a, self.__wref),
- Color((h2, s, l), 'hsl', self.__a, self.__wref),
- Color((h3, s, l), 'hsl', self.__a, self.__wref))
-
- def AnalogousScheme(self, angle=30, mode='ryb'):
- '''Return two colors analogous to this one.
-
- Args:
- :angle:
- The angle between the hues of the created colors and this one.
- :mode:
- Select which color wheel to use for the generation (ryb/rgb).
-
- Returns:
- A tuple of grapefruit.Colors analogous to this one.
-
- >>> c1 = Color.NewFromHsl(30, 1, 0.5)
-
- >>> c2, c3 = c1.AnalogousScheme()
- >>> c2.hsl
- (330, 1, 0.5)
- >>> c3.hsl
- (90, 1, 0.5)
-
- >>> c2, c3 = c1.AnalogousScheme(10)
- >>> c2.hsl
- (20, 1, 0.5)
- >>> c3.hsl
- (40, 1, 0.5)
-
- '''
- h, s, l = self.__hsl
-
- if mode == 'ryb': h = Color.RgbToRyb(h)
- h += 360
- h1 = (h - angle) % 360
- h2 = (h + angle) % 360
- if mode == 'ryb':
- h1 = Color.RybToRgb(h1)
- h2 = Color.RybToRgb(h2)
-
- return (Color((h1, s, l), 'hsl', self.__a, self.__wref),
- Color((h2, s, l), 'hsl', self.__a, self.__wref))
-
- def AlphaBlend(self, other):
- '''Alpha-blend this color on the other one.
-
- Args:
- :other:
- The grapefruit.Color to alpha-blend with this one.
-
- Returns:
- A grapefruit.Color instance which is the result of alpha-blending
- this color on the other one.
-
- >>> c1 = Color.NewFromRgb(1, 0.5, 0, 0.2)
- >>> c2 = Color.NewFromRgb(1, 1, 1, 0.8)
- >>> c3 = c1.AlphaBlend(c2)
- >>> str(c3)
- '(1, 0.875, 0.75, 0.84)'
-
- '''
- # get final alpha channel
- fa = self.__a + other.__a - (self.__a * other.__a)
-
- # get percentage of source alpha compared to final alpha
- if fa==0: sa = 0
- else: sa = min(1.0, self.__a/other.__a)
-
- # destination percentage is just the additive inverse
- da = 1.0 - sa
-
- sr, sg, sb = [v * sa for v in self.__rgb]
- dr, dg, db = [v * da for v in other.__rgb]
-
- return Color((sr+dr, sg+dg, sb+db), 'rgb', fa, self.__wref)
-
- def Blend(self, other, percent=0.5):
- '''Blend this color with the other one.
-
- Args:
- :other:
- the grapefruit.Color to blend with this one.
-
- Returns:
- A grapefruit.Color instance which is the result of blending
- this color on the other one.
-
- >>> c1 = Color.NewFromRgb(1, 0.5, 0, 0.2)
- >>> c2 = Color.NewFromRgb(1, 1, 1, 0.6)
- >>> c3 = c1.Blend(c2)
- >>> str(c3)
- '(1, 0.75, 0.5, 0.4)'
-
- '''
- dest = 1.0 - percent
- rgb = tuple(((u * percent) + (v * dest) for u, v in zip(self.__rgb, other.__rgb)))
- a = (self.__a * percent) + (other.__a * dest)
- return Color(rgb, 'rgb', a, self.__wref)
-
-def _test():
- import doctest
- reload(doctest)
- doctest.testmod()
-
-if __name__=='__main__':
- _test()
diff --git a/forum_modules/pgfulltext/DISABLED b/forum_modules/pgfulltext/DISABLED
deleted file mode 100644
index e69de29b..00000000
diff --git a/forum_modules/pgfulltext/__init__.py b/forum_modules/pgfulltext/__init__.py
deleted file mode 100644
index 8215e1a9..00000000
--- a/forum_modules/pgfulltext/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-NAME = 'Postgresql Full Text Search'
-DESCRIPTION = "Enables PostgreSql full text search functionality."
-
-try:
- import psycopg2
- CAN_ENABLE = True
-except:
- CAN_ENABLE = False
-
\ No newline at end of file
diff --git a/forum_modules/pgfulltext/handlers.py b/forum_modules/pgfulltext/handlers.py
deleted file mode 100644
index f4a7a3b2..00000000
--- a/forum_modules/pgfulltext/handlers.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from forum.models import Question
-
-def question_search(keywords, orderby):
- return Question.objects.filter(deleted=False).extra(
- select={
- 'ranking': "ts_rank_cd(tsv, plainto_tsquery(%s), 32)",
- },
- where=["tsv @@ plainto_tsquery(%s)"],
- params=[keywords],
- select_params=[keywords]
- ).order_by(orderby, '-ranking')
\ No newline at end of file
diff --git a/forum_modules/pgfulltext/management.py b/forum_modules/pgfulltext/management.py
deleted file mode 100644
index 15ba3bd7..00000000
--- a/forum_modules/pgfulltext/management.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import os
-
-from django.db import connection, transaction
-from django.conf import settings
-
-import forum.models
-
-if settings.DATABASE_ENGINE in ('postgresql_psycopg2', 'postgresql', ):
- from django.db.models.signals import post_syncdb
-
- def setup_pgfulltext(sender, **kwargs):
- if sender == forum.models:
- install_pg_fts()
-
- post_syncdb.connect(setup_pgfulltext)
-
-def install_pg_fts():
- f = open(os.path.join(os.path.dirname(__file__), 'pg_fts_install.sql'), 'r')
-
- try:
- try:
- cursor = connection.cursor()
- cursor.execute(f.read())
- transaction.commit_unless_managed()
- except:
- pass
- finally:
- cursor.close()
-
- f.close()
diff --git a/forum_modules/pgfulltext/pg_fts_install.sql b/forum_modules/pgfulltext/pg_fts_install.sql
deleted file mode 100644
index 72eca516..00000000
--- a/forum_modules/pgfulltext/pg_fts_install.sql
+++ /dev/null
@@ -1,38 +0,0 @@
-ALTER TABLE question ADD COLUMN tsv tsvector;
-
-CREATE OR REPLACE FUNCTION public.create_plpgsql_language ()
- RETURNS TEXT
- AS $$
- CREATE LANGUAGE plpgsql;
- SELECT 'language plpgsql created'::TEXT;
- $$
-LANGUAGE 'sql';
-
-SELECT CASE WHEN
- (SELECT true::BOOLEAN
- FROM pg_language
- WHERE lanname='plpgsql')
- THEN
- (SELECT 'language already installed'::TEXT)
- ELSE
- (SELECT public.create_plpgsql_language())
- END;
-
-DROP FUNCTION public.create_plpgsql_language ();
-
-CREATE OR REPLACE FUNCTION set_question_tsv() RETURNS TRIGGER AS $$
-begin
- new.tsv :=
- setweight(to_tsvector('english', coalesce(new.tagnames,'')), 'A') ||
- setweight(to_tsvector('english', coalesce(new.title,'')), 'B') ||
- setweight(to_tsvector('english', coalesce(new.summary,'')), 'C');
- RETURN new;
-end
-$$ LANGUAGE plpgsql;
-
-CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
-ON question FOR EACH ROW EXECUTE PROCEDURE set_question_tsv();
-
- CREATE INDEX question_tsv ON question USING gin(tsv);
-
-UPDATE question SET title = title;
diff --git a/forum_modules/robotstxt/DISABLED b/forum_modules/robotstxt/DISABLED
deleted file mode 100755
index e69de29b..00000000
diff --git a/forum_modules/robotstxt/__init__.py b/forum_modules/robotstxt/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/forum_modules/robotstxt/templates/robots.txt b/forum_modules/robotstxt/templates/robots.txt
deleted file mode 100755
index 574fc315..00000000
--- a/forum_modules/robotstxt/templates/robots.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-User-agent: *
-Disallow: /
\ No newline at end of file
diff --git a/forum_modules/robotstxt/urls.py b/forum_modules/robotstxt/urls.py
deleted file mode 100644
index 79a6d84c..00000000
--- a/forum_modules/robotstxt/urls.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.conf.urls.defaults import *
-from django.views.generic.simple import direct_to_template
-
-urlpatterns = patterns('',
- (r'^robots.txt$', direct_to_template, {'template': 'modules/robotsdennyall/robots.txt'}),
-)
diff --git a/forum_modules/sphinxfulltext/DISABLED b/forum_modules/sphinxfulltext/DISABLED
deleted file mode 100644
index e69de29b..00000000
diff --git a/forum_modules/sphinxfulltext/__init__.py b/forum_modules/sphinxfulltext/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/forum_modules/sphinxfulltext/dependencies.py b/forum_modules/sphinxfulltext/dependencies.py
deleted file mode 100644
index 046ebfc5..00000000
--- a/forum_modules/sphinxfulltext/dependencies.py
+++ /dev/null
@@ -1,2 +0,0 @@
-DJANGO_APPS = ('djangosphinx', )
-
diff --git a/forum_modules/sphinxfulltext/handlers.py b/forum_modules/sphinxfulltext/handlers.py
deleted file mode 100644
index 226acf72..00000000
--- a/forum_modules/sphinxfulltext/handlers.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from forum.models import Question
-
-def question_search(keywords, orderby):
- return Question.search.query(keywords)
\ No newline at end of file
diff --git a/forum_modules/sphinxfulltext/models.py b/forum_modules/sphinxfulltext/models.py
deleted file mode 100644
index a188728d..00000000
--- a/forum_modules/sphinxfulltext/models.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from forum.models import Question
-from django.conf import settings
-from djangosphinx.manager import SphinxSearch
-
-from djangosphinx.models import SphinxSearch
-
-Question.add_to_class('search', SphinxSearch(
- index=' '.join(settings.SPHINX_SEARCH_INDICES),
- mode='SPH_MATCH_ALL',
- )
- )
diff --git a/forum_modules/sphinxfulltext/settings.py b/forum_modules/sphinxfulltext/settings.py
deleted file mode 100644
index 564404c6..00000000
--- a/forum_modules/sphinxfulltext/settings.py
+++ /dev/null
@@ -1,5 +0,0 @@
-SPHINX_API_VERSION = 0x113 #refer to djangosphinx documentation
-SPHINX_SEARCH_INDICES=('askbot',) #a tuple of index names remember about a comma after the
-#last item, especially if you have just one :)
-SPHINX_SERVER='localhost'
-SPHINX_PORT=3312
diff --git a/livesettings/README b/livesettings/README
new file mode 100644
index 00000000..6fe70cc5
--- /dev/null
+++ b/livesettings/README
@@ -0,0 +1,4 @@
+this is very slightly forked version of django-livesettings
+for use in the askbot forum project
+
+will attempt to re-merge into the original django-livesettings
diff --git a/rmpyc b/rmpyc
deleted file mode 100755
index 014575f6..00000000
--- a/rmpyc
+++ /dev/null
@@ -1 +0,0 @@
-rm `find . -name '*.pyc'`
diff --git a/run b/run
deleted file mode 100755
index 06279161..00000000
--- a/run
+++ /dev/null
@@ -1 +0,0 @@
-python manage.py runserver `hostname -i`:8000
diff --git a/run-tests b/run-tests
deleted file mode 100755
index 642b8a0a..00000000
--- a/run-tests
+++ /dev/null
@@ -1 +0,0 @@
-python manage.py test forum
--
cgit v1.2.3-1-g7c22