moved qdb here because matt is lazy master
authorJacob Parker <j3parker@csclub.uwaterloo.ca>
Fri, 16 Mar 2012 01:41:53 +0000 (21:41 -0400)
committerJacob Parker <j3parker@csclub.uwaterloo.ca>
Fri, 16 Mar 2012 01:41:53 +0000 (21:41 -0400)
83 files changed:
pub/qdb/.htaccess [new file with mode: 0644]
pub/qdb/.install.txt.swp [new file with mode: 0644]
pub/qdb/changelog.txt [new file with mode: 0644]
pub/qdb/chirpy.ini [new file with mode: 0644]
pub/qdb/index.cgi [new file with mode: 0755]
pub/qdb/install.txt [new file with mode: 0644]
pub/qdb/license.txt [new file with mode: 0644]
pub/qdb/readme.txt [new file with mode: 0644]
pub/qdb/res/.htaccess [new file with mode: 0644]
pub/qdb/res/captcha/.htaccess [new file with mode: 0644]
pub/qdb/res/themes/default/cite.ico [new file with mode: 0644]
pub/qdb/res/themes/default/css/default.css [new file with mode: 0644]
pub/qdb/res/themes/default/css/styles/default.css [new file with mode: 0644]
pub/qdb/res/themes/default/css/styles/fish_tank.css [new file with mode: 0644]
pub/qdb/res/themes/default/css/styles/grayscale.css [new file with mode: 0644]
pub/qdb/res/themes/default/css/styles/nineties.css [new file with mode: 0644]
pub/qdb/res/themes/default/css/styles/spring.css [new file with mode: 0644]
pub/qdb/res/themes/default/js/administration.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/ajax.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/excanvas/AUTHORS [new file with mode: 0644]
pub/qdb/res/themes/default/js/excanvas/COPYING [new file with mode: 0644]
pub/qdb/res/themes/default/js/excanvas/README [new file with mode: 0644]
pub/qdb/res/themes/default/js/excanvas/examples/example1.html [new file with mode: 0644]
pub/qdb/res/themes/default/js/excanvas/examples/example2.html [new file with mode: 0644]
pub/qdb/res/themes/default/js/excanvas/examples/example3.html [new file with mode: 0644]
pub/qdb/res/themes/default/js/excanvas/examples/ff.jpg [new file with mode: 0644]
pub/qdb/res/themes/default/js/excanvas/excanvas.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/graph.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/live_rating.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/slider/boxsizing.htc [new file with mode: 0644]
pub/qdb/res/themes/default/js/slider/range.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/slider/slider.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/slider/timer.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/style_switcher.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/tabbed_pane.js [new file with mode: 0644]
pub/qdb/res/themes/default/js/tag_cloud.js [new file with mode: 0644]
pub/qdb/src/.htaccess [new file with mode: 0644]
pub/qdb/src/cache/template/05/97c435de1f438f8dcb708dc88fa3f9 [new file with mode: 0644]
pub/qdb/src/cache/template/3e/46cdf3a4a085f1b3af728e719f8c06 [new file with mode: 0644]
pub/qdb/src/cache/template/76/3e31852f24c555c67307d4f934cdae [new file with mode: 0644]
pub/qdb/src/locales/en-US.ini [new file with mode: 0644]
pub/qdb/src/modules/Chirpy.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/Account.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/Configuration.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/DataManager.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/DataManager/MySQL.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/Event.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/Locale.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/NewsItem.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/Quote.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UI.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UI/WebApp.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UI/WebApp/Administration.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UI/WebApp/Captcha.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UI/WebApp/Captcha/Authen_Captcha.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UI/WebApp/Captcha/GD_SecurityImage.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UI/WebApp/Session.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UI/WebApp/Session/DataManager.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/UpdateChecker.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/Util.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/Util/IniFile.pm [new file with mode: 0644]
pub/qdb/src/modules/Chirpy/Webauth.pm [new file with mode: 0644]
pub/qdb/src/templates/default/_foot.html [new file with mode: 0644]
pub/qdb/src/templates/default/_head.html [new file with mode: 0644]
pub/qdb/src/templates/default/_search_form.html [new file with mode: 0644]
pub/qdb/src/templates/default/administration.html [new file with mode: 0644]
pub/qdb/src/templates/default/confirm.html [new file with mode: 0644]
pub/qdb/src/templates/default/error.html [new file with mode: 0644]
pub/qdb/src/templates/default/feeds/atom10.xml [new file with mode: 0644]
pub/qdb/src/templates/default/feeds/rss20.xml [new file with mode: 0644]
pub/qdb/src/templates/default/login.html [new file with mode: 0644]
pub/qdb/src/templates/default/message.html [new file with mode: 0644]
pub/qdb/src/templates/default/quote_list.html [new file with mode: 0644]
pub/qdb/src/templates/default/quote_search.html [new file with mode: 0644]
pub/qdb/src/templates/default/start_page.html [new file with mode: 0644]
pub/qdb/src/templates/default/statistics.html [new file with mode: 0644]
pub/qdb/src/templates/default/submit_quote.html [new file with mode: 0644]
pub/qdb/src/templates/default/tag_cloud.html [new file with mode: 0644]
pub/qdb/src/welcome.html [new file with mode: 0644]
pub/qdb/util/chirpy_rqms_import.php [new file with mode: 0644]
pub/qdb/util/gzip.pl [new file with mode: 0644]
pub/qdb/util/setup.pl [new file with mode: 0644]
templates/default.html

diff --git a/pub/qdb/.htaccess b/pub/qdb/.htaccess
new file mode 100644 (file)
index 0000000..122045a
--- /dev/null
@@ -0,0 +1,22 @@
+<IfModule mod_rewrite.c>\r
+RewriteEngine On\r
+RewriteRule  ^([0-9]+|0x[0-9A-Fa-f]+|0b[01]+)$  index.cgi?id=$1  [L]\r
+RewriteRule  ^(rss|atom)$  %{REQUEST_URI}/qotw  [L,R=301]\r
+RewriteRule  ^(rss|atom)/([a-z_]+)$  index.cgi?action=$2&output=$1  [L]\r
+RewriteRule  ^ms/([a-z_]+)$  index.cgi?action=$1&output=ms  [L]\r
+RewriteRule  ^search/(.+)  index.cgi?action=search&query=$1  [L]\r
+RewriteRule  ^tags/([^\ ]+)$  index.cgi?action=search&query=tag:$1  [L]\r
+RewriteRule  ^([a-z_]+)(/([a-z_]*))?$  index.cgi?action=$1&admin_action=$3  [L,QSA]\r
+</IfModule>\r
+\r
+<IfModule mod_access.c>\r
+<Files chirpy.ini>\r
+order deny,allow\r
+deny from all\r
+</Files>\r
+</IfModule>\r
+\r
+AuthType WebAuth\r
+require valid-user\r
+\r
+AddHandler cgi-script .cgi\r
diff --git a/pub/qdb/.install.txt.swp b/pub/qdb/.install.txt.swp
new file mode 100644 (file)
index 0000000..f2b0fab
Binary files /dev/null and b/pub/qdb/.install.txt.swp differ
diff --git a/pub/qdb/changelog.txt b/pub/qdb/changelog.txt
new file mode 100644 (file)
index 0000000..c5e0afc
--- /dev/null
@@ -0,0 +1,111 @@
+ _____________________________________________________________________________\r
+/                                                                             \\r
+| Chirpy! v0.3                                                     2007-02-09 |\r
+\_____________________________________________________________________________/\r
+\r
+* Added statistics page\r
+* Users can now revert their vote if, for example, they accidentally clicked\r
+  on the wrong link\r
+* Now keeping (and displaying) vote counts per quote\r
+* Radically changed order of top and bottom quotes, by introducing quote\r
+  scores: score = (positive votes + 1) / (negative votes + 1), as suggested by\r
+  sp3000 at irc.mozilla.org #bs on 2006-10-28\r
+* Added metadata-based logging, including search\r
+* Made rating and reporting links use POST instead of GET and added\r
+  intermediate confirmation page for non-AJAX users\r
+* Added microsummaries\r
+* You can now edit quotes before approving them\r
+* Added wildcard searching\r
+* Made tag cloud ignore unapproved quotes\r
+* Tag cloud can now use logarithmic calculation, to make tag use distribution\r
+  appear more even\r
+* Added dynamic tag cloud pruning based on number of quotes\r
+* Administrative interface now only displays tabs that are available\r
+* Improved and simplified captcha support; added preliminary support for\r
+  GD::SecurityImage as an alternative for Authen::Captcha; to enable captchas,\r
+  simply webapp.captcha_provider=Authen_Captcha should suffice\r
+* Made pages point to their own feeds instead of QotW (where applicable)\r
+* Added description field in chirpy.ini, for use in feeds\r
+* Made quote titles in feeds more meaningful\r
+* Put page title in feed title instead of subtitle\r
+* Improved feed modification detection\r
+* Made templates use HTML::Template's caching feature, which is pretty fast\r
+* Template parameters are now global, which means you can include parameters\r
+  from outside a loop\r
+* Renamed template parameters for search form; they are now always available\r
+  and the default theme includes a search form with search results\r
+* Optimized template parsing a little\r
+* Moved administration-related subs to a separate class, speeding up non-admin\r
+  pages somewhat\r
+* Extended quick style linking method: <> is now omitted and link text can be\r
+  changed by separating it from URL by whitespace, e.g.\r
+    Surf to <http://chirpy.sourceforge.net/ The Chirpy! web site>!\r
+* Simplified RSS feed: using HTML in <description> and removing\r
+  <content:encoded> and <xhtml:body>, making feedvalidator.org like it more\r
+* Added tags as categories in feeds\r
+* Made check for expired sessions a lot faster\r
+* News body on start page is now divided into paragraphs; opening and closing\r
+  tag are included, so update your templates\r
+* Bugfix: live rating no longer throws a JavaScript error in IE\r
+* Bugfix: quote rating up log entry now includes quote ID (#1493589)\r
+* Just for fun, quote IDs can now be in binary and hex notation too, as per\r
+  <http://quotes.burntelectrons.org/844>\r
+\r
+ _____________________________________________________________________________\r
+/                                                                             \\r
+| Chirpy! v0.2                                                     2006-05-02 |\r
+\_____________________________________________________________________________/\r
+\r
+* Fixed SQL injection vulnerabilities\r
+* Fixed logging of author when editing or removing news items (#1289047)\r
+* Added on-the-fly gzip compression to Chirpy::UI::WebApp--webapp.enable_gzip=1\r
+  in your configuration file enables it\r
+* Added optional captcha image to Chirpy::UI::WebApp's quote submission page\r
+* Added quote tagging\r
+* Made search query Google-style and added tag: prefix for searching for quotes\r
+  with a certain tag\r
+* Made top and bottom quotes browsable\r
+* Added periodic update check (site owners only)\r
+* Made ui.quotes_per_page apply to random, top and bottom quotes instead of\r
+  individual setting per page type\r
+* Added webapp.quotes_per_feed to set maximum number of quotes in feeds\r
+  individually\r
+* Made Atom 1.0 feed valid by adding feed ID and webmaster name. Webmaster name\r
+  must be configured as webapp.webmaster_name\r
+* Added quote_count method to Chirpy::DataManager and APPROVED_QUOTE_COUNT,\r
+  UNAPPROVED_QUOTE_COUNT, and TOTAL_QUOTE_COUNT to templates\r
+* Added mass quote approval and unflagging to Chirpy::UI::WebApp's\r
+  administration section\r
+* Changed Chirpy::DataManager's API so add_* methods set IDs\r
+* Added option to automatically turn URLs and e-mail addresses in quotes into\r
+  hyperlinks\r
+* Made Chirpy::UI::WebApp escape all e-mail addresses to prevent spam\r
+* Made Chirpy::UI::WebApp replace sequences of whitespaces with &#xA0; instead\r
+  of &nbsp;, so the Atom feed remains valid\r
+* Made quote reporting require session information to prevent false positives\r
+  from crawlers\r
+* Fixed sub account_count in Chirpy::DataManager::MySQL; removing accounts now\r
+  works again\r
+* Optimized fetching single quote in Chirpy::DataManager::MySQL\r
+* Made Chirpy::UI::WebApp::Session automatically remove expired sessions every\r
+  24 hours; util/remove_expired_sessions.pl is now obsolete\r
+* Fixed US English (and Dutch) locale: quote_submission_thanks_administrator is\r
+  now quote_submission_thanks_no_approval\r
+* Extended feed templates with a couple of variables and added rating and\r
+  report URLs as well as notes to the default templates\r
+* Replaced feed templates' CSS with legacy HTML\r
+* Cosmetic fixes to Chirpy::UI::WebApp's live rating system\r
+* Mentioned Chirpy::UI::WebApp::Session::DataManager in Chirpy::DataManager's\r
+  documentation\r
+* Cosmetic fix in Account Manager: space after New Account\r
+* Replaced table for vertical split on start page with divs\r
+* No longer overriding old onunload function in style switcher\r
+* Added some debugging features\r
+* Added changelog.txt\r
+\r
+ _____________________________________________________________________________\r
+/                                                                             \\r
+| Chirpy! v0.1                                                     2005-09-12 |\r
+\_____________________________________________________________________________/\r
+\r
+* First official release
\ No newline at end of file
diff --git a/pub/qdb/chirpy.ini b/pub/qdb/chirpy.ini
new file mode 100644 (file)
index 0000000..1aedd56
--- /dev/null
@@ -0,0 +1,42 @@
+[general]
+title=CSC Quote Database
+description=Quotes from the CSC
+base_path=./src
+locale=en-US
+rating_limit_count=60
+rating_limit_time=60
+update_check=1
+
+[data]
+type=MySQL
+mysql.hostname=localhost
+mysql.port=3306
+mysql.username=mimcpher
+mysql.password=oq5VuqPtNfLTFRZozDsH
+mysql.database=mimcpher
+mysql.prefix=qdb_
+
+[ui]
+type=WebApp
+date_time_format=%Y-%m-%d %H:%M GMT
+date_format=%Y-%m-%d
+time_format=%H:%M GMT
+use_gmt=1
+quotes_per_page=30
+recent_news_item=3
+moderation_queue_public=1
+tac_cloud_logarithmic=1
+webapp.webmaster_name=Calum T. Dalek
+webapp.webmaster_email=calum@csclub.uwaterloo.ca
+webapp.site_url=https://csclub.uwaterloo.ca/~j3parker/pub/qdb/
+webapp.resources_url=https://csclub.uwaterloo.ca/~j3parker/pub/qdb/res
+webapp.theme=default
+webapp.welcome_text_file=welcome.html
+webapp.cookie_domain=csclub.uwaterloo.ca
+webapp.cookie_path=/~j3parker/pub/qdb
+webapp.session_expiry=+3d
+webapp.enable_short_urls=0
+webapp.enable_feeds=1
+webapp.quotes_per_feed=50
+webapp.enable_gzip=0
+webapp.enable_autolink=1
diff --git a/pub/qdb/index.cgi b/pub/qdb/index.cgi
new file mode 100755 (executable)
index 0000000..fc6b03d
--- /dev/null
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+
+###############################################################################
+# Chirpy! 0.3, a quote management system                                      #
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #
+###############################################################################
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; either version 2 of the License, or (at your option)   #
+# any later version.                                                          #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #
+###############################################################################
+
+###############################################################################
+# index.cgi                                                                   #
+# Initialization script                                                       #
+###############################################################################
+# $Id:: index.cgi 291 2007-02-05 21:24:46Z ceetee                           $ #
+###############################################################################
+
+use strict;
+use warnings;
+use CGI::Carp qw(fatalsToBrowser set_message);
+
+BEGIN {
+       unshift @INC, 'src/modules';
+       set_message(sub {
+               my $msg = shift;
+               print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"', $/,
+                       '"http://www.w3.org/TR/html4/strict.dtd">', $/,
+                       '<html>', $/,
+                       '<head>', $/,
+                       '<meta http-equiv="Content-Type"', $/,
+                       'content="text/html; charset=UTF-8">', $/,
+                       '<title>An Error Occurred</title>', $/,
+                       '</head>', $/,
+                       '<body>', $/,
+                       '<h1>An Error Occurred</h1>', $/,
+                       '<blockquote><pre>', $msg, '</pre></blockquote>', $/,
+                       '<p><em>Powered by <a', $/,
+                       'href="http://chirpy.sourceforge.net/">Chirpy!</a></em></p>', $/,
+                       '</body>', $/,
+                       '</html>';
+               exit;
+       });
+}
+
+use Chirpy 0.3;
+
+chirpy('./chirpy.ini');
+
+###############################################################################
diff --git a/pub/qdb/install.txt b/pub/qdb/install.txt
new file mode 100644 (file)
index 0000000..99d7736
--- /dev/null
@@ -0,0 +1,533 @@
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# INSTALLATION INSTRUCTIONS                                                   #\r
+###############################################################################\r
+\r
+\r
+CONTENTS\r
+========\r
+\r
+1. INTRODUCTION\r
+\r
+2. REQUIREMENTS\r
+\r
+3. INSTALLATION\r
+3.1. Copy files\r
+3.2. Configure\r
+3.2.1. The [general] Section\r
+3.2.2. The [data] Section\r
+3.2.3. The [ui] Section\r
+3.3. Setup\r
+3.4. Import\r
+\r
+4. UPGRADING\r
+\r
+5. RUN CHIRPY!\r
+\r
+6. NEXT STEPS\r
+6.1. Your Own Theme\r
+6.2. Your Own Locale\r
+6.3. Your Own Data Manager\r
+6.4. Your Own User Interface\r
+\r
+\r
+1. INTRODUCTION\r
+===============\r
+\r
+Thank you for trying this Chirpy! beta version. Note that, while every feature\r
+contained in this product is functional unless stated otherwise, you may very\r
+well run into problems here and there. However, in most cases, there's probably\r
+an easy fix. You may find the following URLs helpful:\r
+\r
+* Trackers (Bugs, Support, Feature Requests)\r
+  http://sourceforge.net/tracker/?group_id=147270\r
+\r
+* Chirpy! web site\r
+  http://chirpy.sourceforge.net/\r
+\r
+* API Specification (mainly for developers)\r
+  http://chirpy.sourceforge.net/pod/\r
+\r
+\r
+2. REQUIREMENTS\r
+===============\r
+\r
+To install Chirpy!, you'll need web space that gives you access to Perl 5.8 and\r
+a SQL server running MySQL 4.1 or higher. Those version numbers are expected\r
+to drop a little in an upcoming version.\r
+\r
+In addition, you'll need these Perl modules:\r
+\r
+Carp                Data::Dumper        HTML::Template\r
+Digest::MD5         DBD::mysql          HTTP::Date\r
+Encode              DBI                 URI::Escape\r
+POSIX               Storable\r
+\r
+It seems like a long list, but most of them are common and come with standard\r
+distributions of Perl. Other modules will need to be installed separately; you\r
+should probably ask your host about that. If any of these modules are missing,\r
+Chirpy! will not work.\r
+\r
+In addition, Chirpy! uses the CGI::Carp module for verbose error reporting,\r
+which makes problems easier to trace. While this module is also really common,\r
+you can drop the dependency by removing statements containing "CGI::Carp" or\r
+"set_message" from the scripts.\r
+\r
+\r
+3. INSTALLATION\r
+===============\r
+\r
+If all the requirements are met, you are ready to begin the installation of\r
+Chirpy!. Let's go!\r
+\r
+3.1. Copy files\r
+---------------\r
+\r
+Since you're reading this file, we'll assume that you've already extracted the\r
+entire installation package.\r
+\r
+First, we'll take a quick look at the contents of the package. You should have\r
+the following directories and files in the root:\r
+\r
+res/                The resources directory. Resources are public files that\r
+                    are required by themes, such as images and style sheets.\r
+src/                The source directory. Contains all the necessary files to\r
+                    run Chirpy!, including its modules, locales, templates and\r
+                    configuration file.\r
+util/               Contains some utilities that you may find useful while\r
+                    installing or using Chirpy!.\r
+.htaccess           Has some directives that enable short URLs. Covered later\r
+                    in this document.\r
+changelog.txt       All the stuff that's changed between Chirpy! releases.\r
+index.cgi           The main script. Will be the only URL that is accessed\r
+                    directly by visitors.\r
+install.txt         This document.\r
+license.txt         The GNU General Public License, under which Chirpy! is\r
+                    distributed.\r
+\r
+Now, we're going the copy the files to a public path on the web server. This is\r
+usually done using an FTP client.\r
+\r
+As for the files in the package root directory, the only one you really need is\r
+index.cgi. Since this is a Perl script, it has to be inside a path where the\r
+server allows execution of scripts. Most servers these days will allow that\r
+anywhere, but yours might require that you put it inside a cgi-bin/ directory,\r
+which may even be outside the document root on the FTP server. This is no\r
+problem--just make sure it wants to execute it and not display the contents.\r
+\r
+The index.cgi file needs some attributes. This is done by issueing a SITE CHMOD\r
+command on the FTP server. If you use a graphical FTP client, you can probably\r
+right-click on the file and tick off the necessary attributes. They are:\r
+\r
+                    Owner     READ      WRITE     EXECUTE\r
+                    Group     READ      -         EXECUTE\r
+                    Others    READ      -         EXECUTE\r
+\r
+This translates to the string representation "rwxr-xr-x" or, numerically, 755\r
+on UNIX systems. On most systems, this step is essential. You will get an error\r
+page if you fail to change the file's attributes. Also, if you are using an FTP\r
+client, make sure you have uploaded index.cgi in ASCII (text) mode, not image\r
+(binary).\r
+\r
+You may want to copy the .htaccess file from the package root as well. Make\r
+sure you don't confuse it with the one in the src/ directory! The one covered\r
+here allows you to use short URLs, which are easy to remember, using the\r
+mod_rewrite Apache module. To use that option, you will need to run Chirpy! on\r
+an Apache web server that has the mod_rewrite module. Note that, while short\r
+URLs will not work if mod_rewrite is not enabled, it should be safe to have the\r
+.htaccess file there anyway, since it checks for the module first. Moreover, it\r
+also attempts to block access to the configuration file, so you might want to\r
+have it anyway; we will cover that configuration file in a moment.\r
+\r
+The res/ directory only holds static files and should reside in a public path\r
+on the server, like <http://www.yourserver.com/chirpy/res>. You can upload it\r
+anywhere, even on a different server. We'll need the URL later for the\r
+configuration.\r
+\r
+Next up: the src/ directory. This one does not have to be publicly accessible\r
+and for security reasons, it really shouldn't be either. Chirpy! takes some\r
+precautions to disallow access to it by placing a .htaccess file in it, that\r
+holds code specific to the Apache web server. However, placing the directory\r
+outside the document root (if possible) is a MUCH safer choice.\r
+Alternatively, you can keep chirpy.ini, the configuration file, which we will\r
+cover later in this document, outside the document root and the src/ directory.\r
+\r
+Inside the src/ directory, you will find a directory called cache/. Make sure\r
+it is empty. Also, it needs to be world-writable, i.e. its attributes must be\r
+set to "rwxrwxrwx" or 777.\r
+\r
+The util/ directory should NOT be uploaded. You are however likely to need the\r
+setup.pl script inside it, but we'll get to that in a moment.\r
+\r
+That covers uploading the files--almost. We'll need to upload a configuration\r
+file at the end of the next step.\r
+\r
+3.2. Configure\r
+--------------\r
+\r
+Chirpy! stores its configuration in a standard .INI file, a basic format which\r
+is common on Windows systems. In this step, we'll create such a configuration\r
+file.\r
+\r
+By default, Chirpy! looks for chirpy.ini in the working directory (which is\r
+where you put index.cgi) and inside the src/ directory if it is directly inside\r
+the working directory. Otherwise, you will have to edit index.cgi. As explained\r
+above, that little bit of editing is recommended, so your configuration file\r
+resides outside the public domain. After all, it will contain your MySQL server\r
+password, and we don't want visitors to read that, now, do we?\r
+\r
+Right, let's create that chirpy.ini file now. Here is a basic example:\r
+\r
+-------------------------------------------------------------------------------\r
+[general]\r
+title=My Little QDB\r
+description=A place for my quotes\r
+base_path=./src\r
+locale=en-US\r
+rating_limit_count=2\r
+rating_limit_time=60\r
+update_check=1\r
+\r
+[data]\r
+type=MySQL\r
+mysql.hostname=localhost\r
+mysql.port=3306\r
+mysql.username=my_username\r
+mysql.password=my_password\r
+mysql.database=my_database\r
+mysql.prefix=chirpy_\r
+\r
+[ui]\r
+type=WebApp\r
+date_time_format=%Y-%m-%d %H:%M GMT\r
+date_format=%Y-%m-%d\r
+time_format=%H:%M GMT\r
+use_gmt=1\r
+quotes_per_page=10\r
+recent_news_items=3\r
+moderation_queue_public=1\r
+tag_cloud_logarithmic=1\r
+webapp.webmaster_name=John Doe\r
+webapp.webmaster_email=you@yourserver.com\r
+webapp.site_url=http://www.yourserver.com/cgi-bin/chirpy\r
+webapp.resources_url=http://www.yourserver.com/chirpy/res\r
+webapp.theme=default\r
+webapp.welcome_text_file=welcome.html\r
+webapp.cookie_domain=yourserver.com\r
+webapp.cookie_path=/cgi-bin/chirpy\r
+webapp.session_expiry=+3d\r
+webapp.enable_short_urls=0\r
+webapp.enable_feeds=0\r
+webapp.quotes_per_feed=50\r
+webapp.enable_gzip=0\r
+webapp.enable_autolink=0\r
+-------------------------------------------------------------------------------\r
+\r
+A lot of the values don't look very interesting right now, but we'll have to\r
+change some of the others.\r
+\r
+3.2.1. The [general] Section\r
+----------------------------\r
+\r
+title               Change this value to the title you want your QDB to have.\r
+description         Enter a brief description of the purpose of your QDB.\r
+base_path           Enter the absolute or relative path to the src/ directory\r
+                    here. When using relative paths, this is again relative to\r
+                    the directory where index.cgi is.\r
+update_check        Set this to 1 to tell Chirpy! to automatically check for\r
+                    updates periodically. Only site owners will be informed of\r
+                    available updates. This feature requires that libwww-perl\r
+                    (LWP) be installed; if it is not, Chirpy! will just show\r
+                    you an informative error message.\r
+\r
+3.2.2. The [data] Section\r
+-------------------------\r
+\r
+mysql.hostname      Enter the name of the MySQL server here. Usually, this will\r
+                    be "localhost".\r
+mysql.port          Enter the port the MySQL server uses. The default is 3306.\r
+mysql.username      Enter your MySQL username. This is not necessarily the same\r
+                    as your regular username.\r
+mysql.password      Enter your MySQL password. This is not necessarily the same\r
+                    as your regular password.\r
+mysql.database      Enter the name of the MySQL database Chirpy! should use. If\r
+                    it does not exist, you need to create it first. Do not\r
+                    create any tables; Chirpy! will do that for you.\r
+mysql.prefix        If you only have one MySQL database, Chirpy! can make its\r
+                    tables easy to find by prefixing their names with the text\r
+                    you enter here. The default "chirpy_" is a wise choice.\r
+\r
+3.2.3. The [ui] Section\r
+-----------------------\r
+\r
+webapp.webmaster_name\r
+                    Your name.\r
+webapp.webmaster_email\r
+                    Your e-mail address. Don't worry about spam, Chirpy! will\r
+                    use some fancy tricks to hide it.\r
+webapp.site_url     The URL where you put index.cgi.\r
+webapp.resources_url\r
+                    The URL where you put the res/ directory.\r
+webapp.cookie_domain\r
+                    Essentially the domain name (without the www prefix) from\r
+                    your site's URL. This will be used to store cookies.\r
+webapp.cookie_path  The part that comes after the domain in the site URL. This\r
+                    will also be used to store cookies.\r
+webapp.enable_short_urls\r
+                    Change this to 1 to enable the short URLs feature described\r
+                    above. If you get "Not Found" errors while browsing the QDB\r
+                    later, you should turn it off.\r
+webapp.enable_feeds Chirpy! can offer an RSS 2.0 feed and an Atom 1.0 feed of\r
+                    the Quotes of the Week, so visitors can syndicate them. If\r
+                    you want to enable those, set this to 1.\r
+webapp.quotes_per_feed\r
+                    This is the maximum number of quotes to include in a feed.\r
+                    In theory, Chirpy! can provide a content feed for any page,\r
+                    and since feeds do not offer "Previous"/"Next" links, this\r
+                    should be a sensible number. The default is 50.\r
+webapp.enable_gzip  Chirpy! can greatly decrease bandwidth usage by compressing\r
+                    output on the fly if the browser supports it. Set this to 1\r
+                    to enable that. It requires the Compress::Zlib Perl module.\r
+webapp.enable_autolink\r
+                    Change this to 1 to automatically turn URLs and e-mail\r
+                    addresses in quote bodies into hyperlinks. This feature is\r
+                    still sort of experimental, but should work fine.\r
+webapp.captcha_provider\r
+                    If you wish to prevent malicious users from spamming the\r
+                    quote submission page, you will want to use captcha images.\r
+                    This parameter sets the captcha provider, which will be\r
+                    Authen_Captcha in most cases. Then Authen_Captcha provider\r
+                    relies on Authen::Captcha being installed.\r
+\r
+You may want to turn the captcha feature off at first, so you can test-drive\r
+Chirpy!'s other features. Configuring the captcha feature should be easy in the\r
+case of Authen_Captcha. The alternative is to use GD_SecurityImage. Support for\r
+that one is preliminary for now. If you are interested in using it, please\r
+consult the appropriate documentation.\r
+\r
+That covers the configuration file. Save it as chirpy.ini and, as stressed\r
+before, try to store it at a location on the server which cannot be accessed\r
+using a Web browser.\r
+\r
+Now, we'll have to modify index.cgi a little to tell it where to find the\r
+configuration file. Again, it looks for chirpy.ini in the working directory and\r
+inside src/, but hopefully, it won't be there. So we'll just open index.cgi in\r
+a text editor and change the line\r
+\r
+  chirpy;\r
+\r
+to the following:\r
+\r
+  chirpy('/path/to/chirpy.ini');\r
+\r
+Again, you can use either an absolute path or a path relative to the working\r
+directory. You'll also need this path in the next step.\r
+\r
+While we're editing index.cgi, there are two more things you might have to\r
+change. The first is the path to the modules/ directory inside src/. This path\r
+is stored like:\r
+\r
+  unshift @INC, 'src/modules';\r
+\r
+If you have not placed the src/ directory in the same directory as index.cgi,\r
+update the path so Perl can find the Chirpy! modules.\r
+\r
+The other thing you might have to change is the path to Perl itself. Most\r
+servers have it at /usr/bin/perl, but if yours doesn't, change the first line\r
+of index.cgi to "#!" followed by the exact path to Perl.\r
+\r
+3.3. Setup\r
+----------\r
+\r
+Now that all the files are there, we'll grab setup.pl from the util/ directory\r
+and open it in a text editor. Look for the line that reads\r
+\r
+  my $ch = new Chirpy();\r
+\r
+and change that to\r
+\r
+  my $ch = new Chirpy('/path/to/chirpy.ini');\r
+\r
+using the same path you entered in index.cgi. In addition, you will have to\r
+update the path to src/modules/ and Perl itself again, if you had to do so for\r
+index.cgi.\r
+\r
+Now, upload setup.pl to the directory where index.cgi resides and change its\r
+attributes so they are the same as index.cgi's; as described above, they should\r
+be rwxr-xr-x (755). Again, since this is a Perl script, upload in ASCII mode!\r
+\r
+Now, we're going to call your Web browser into action. Open it and surf to the\r
+URL where setup.pl should be now, e.g.\r
+\r
+  http://www.yourserver.com/cgi-bin/chirpy/setup.pl\r
+\r
+That should give you a basic page, welcoming you to the setup procedure. If\r
+not, let's go over a few common problems ...\r
+\r
+- If you get the source code of setup.pl or maybe a download window, the server\r
+  doesn't allow execution of Perl scripts in that directory. You'll probably\r
+  need to use the cgi-bin directory instead.\r
+\r
+- If you get a Forbidden error, you probably didn't change the script's\r
+  attributes properly, as described above.\r
+\r
+- If you get a fairly verbose error that has line numbers and lots of weird\r
+  characters and other stuff that confuses you in it, the server executed the\r
+  script, but it crashed somewhere along the way. Something may have gone wrong\r
+  with the upload of one or more files; upload them again. If that doesn't do\r
+  any good, paste the error message at the Support Tracker, to which you can\r
+  find the URL at the start of this document.\r
+\r
+- If you get a generic "Internal Server Error" page, the server most likely\r
+  failed to execute the script at an earlier stage. Some files may have gotten\r
+  corrupted in the upload process; try uploading them again. If that doesn't\r
+  help, go over the text above to see if you didn't miss anything. If problems\r
+  persist, your host can give you a copy of the server's error log, which may\r
+  tell you more. If you can get an error message from the log, you can post\r
+  that at the Support Tracker; without it, the error will be nearly impossible\r
+  to trace.\r
+\r
+Assuming you've reached the setup page now, you get to decide if you want to\r
+keep your existing installation if any. If you're installing Chirpy! for the\r
+first time, you should choose to keep data, since it's faster. That should give\r
+you a page with a basic event log for the setup procedure. If it tells you the\r
+setup procedure has been completed, you can go to the next step now. If not,\r
+take a look at the error and see if you can fix things. If not, try the Support\r
+Tracker for assistance.\r
+\r
+3.4. Import\r
+-----------\r
+\r
+This step is optional. It only applies if you have the Rash Quote Management\r
+System installed and you want to migrate its data to Chirpy!. If you have no\r
+idea what this is about, just skip this step.\r
+\r
+To import Rash's data, grab chirpy_rqms_import.php from the util/ directory.\r
+Open it in a text editor and edit the configuration values at the start of the\r
+script. Then place it in the directory where you installed Rash, so it can find \r
+config.php. Surf to chirpy_rqms_import.php and everything should be clear from\r
+there. If anything goes wrong, try the Support Tracker.\r
+\r
+\r
+4. UPGRADING\r
+============\r
+\r
+If you are upgrading from Chirpy! version 0.1 or 0.2, you should just move the\r
+updated setup.pl script from the "util" directory into Chirpy!'s root directory\r
+for a second, make it executable, and surf to it. Chirpy! will then ask you if\r
+you want to perform a fresh installation or an upgrade. Now, DO *NOT* CLICK ON\r
+"FRESH INSTALLATION," because you would lose all your quotes, accounts, etc.\r
+Instead, click the "UPGRADE" button, wait for the page to load, watch Chirpy!\r
+inform you of the successful upgrade, and remove setup.pl again.\r
+\r
+Additionally, as Chirpy! 0.3 offers quite a couple of new features, you will\r
+probably want to play with those. Most of them just come down to adding a line\r
+in the configuration file. For captchas, you will need to create a couple of\r
+directories; just scroll back to section 3.2, where the configuration file is\r
+explained--it's all there. Enjoy!\r
+\r
+\r
+5. RUN CHIRPY!\r
+==============\r
+ _____________________________________________________________________________\r
+|                                                                             |\r
+|    !!! DON'T FORGET TO REMOVE THE SETUP SCRIPT FROM THE SERVER FIRST !!!    |\r
+|_____________________________________________________________________________|\r
+\r
+By now, the setup script should have directed you to your brand new Chirpy!\r
+installation already. If not, append /index.cgi to your site URL and open that\r
+URL. That should give you a cute little start page. You should be able to leave\r
+off index.cgi in the URL now--try it. If it doesn't work, you should definitely\r
+turn short URLs off. If something else goes wrong, you should try the Support\r
+Tracker; the URL is at the beginning of this file.\r
+\r
+Now, you're pretty much finished. Except that you should change the default\r
+password. Surf to the administration section, log in as "superuser" with the\r
+password "password" (if you didn't poke at setup.pl already) and click on the\r
+"Manage Accounts" option. Select the "superuser" account and modify it.\r
+\r
+That concludes the installation of Chirpy!. I hope you'll enjoy it. If you run\r
+into a problem or you'd like to see a new feature in the next release, surf to\r
+the Trackers.\r
+\r
+Congratulations on a successful installation!\r
+\r
+\r
+6. NEXT STEPS\r
+=============\r
+\r
+Now that you've got a working Chirpy! installation, you can tweak it to your\r
+liking. Most of this is done from the configuration file. If you want to change\r
+the welcome message, you just edit the file welcome.html in the src/ directory,\r
+which is actually a template.\r
+\r
+As Chirpy! is a work in progress, you can expect a lot more documentation on\r
+customizing it in future releases. In the mean time, if you want to do more\r
+advanced tweaking, here are a few possible scenarios:\r
+\r
+6.1. Your Own Theme\r
+-------------------\r
+\r
+The easiest way to do this is to copy the existing "default" theme and modify\r
+it, after which you change webapp.theme in the configuration file to use the\r
+new theme. A theme is nothing but two directories named after it: the one in\r
+src/themes/ holds the templates for the theme, the one in res/ holds its\r
+resources. The template filenames are predefined, so you need to keep those\r
+intact. The templates are parsed by the HTML::Template Perl module, so you will\r
+probably have to look at its documentation for a second--or you could just\r
+learn to use it by looking at the default theme's source code. The manual page\r
+for HTML::Template can be found at\r
+\r
+  http://search.cpan.org/dist/HTML-Template/Template.pm\r
+\r
+6.2. Your Own Locale\r
+--------------------\r
+\r
+You might want Chirpy! in your own language, and you can have it too. If you\r
+look in the src/locales/ directory, you'll see that locales are actually just\r
+INI files. Each locale string, along with some basic instructions for locale\r
+creation, is documented in the POD, located at the URL at the start of this\r
+document.\r
+\r
+6.3. Your Own Data Manager\r
+--------------------------\r
+\r
+Chirpy! is extremely modular. If you'd rather have it store its data in your\r
+choice of database, you are free to implement the Chirpy::DataManager class.\r
+Its API is explained in the POD, which is available at the start of this\r
+document or by typing "perldoc Chirpy::DataManager" from a console or command\r
+prompt, if you have Perl installed.\r
+\r
+6.4. Your Own User Interface\r
+----------------------------\r
+\r
+Apart from creating a backend, you can also write your own frontend class. This\r
+class should implement Chirpy::UI. Unfortunately, the API documentation for it\r
+is not yet available. Note that if your UI would be a web site, you should\r
+probably just create a Chirpy::UI::WebApp theme as described in 5.1.\r
+\r
+If you've created a theme, a locale, a data manager or a UI, please share your\r
+work with the community! E-mail it to me at ceetee@users.sourceforge.net and it\r
+might be included in the next Chirpy! release by default. Obviously, you would\r
+get credit for it.\r
+\r
+\r
+###############################################################################
\ No newline at end of file
diff --git a/pub/qdb/license.txt b/pub/qdb/license.txt
new file mode 100644 (file)
index 0000000..06e25c8
--- /dev/null
@@ -0,0 +1,340 @@
+                    GNU GENERAL PUBLIC LICENSE\r
+                       Version 2, June 1991\r
+\r
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.\r
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\r
+ Everyone is permitted to copy and distribute verbatim copies\r
+ of this license document, but changing it is not allowed.\r
+\r
+                            Preamble\r
+\r
+  The licenses for most software are designed to take away your\r
+freedom to share and change it.  By contrast, the GNU General Public\r
+License is intended to guarantee your freedom to share and change free\r
+software--to make sure the software is free for all its users.  This\r
+General Public License applies to most of the Free Software\r
+Foundation's software and to any other program whose authors commit to\r
+using it.  (Some other Free Software Foundation software is covered by\r
+the GNU Lesser General Public License instead.)  You can apply it to\r
+your programs, too.\r
+\r
+  When we speak of free software, we are referring to freedom, not\r
+price.  Our General Public Licenses are designed to make sure that you\r
+have the freedom to distribute copies of free software (and charge for\r
+this service if you wish), that you receive source code or can get it\r
+if you want it, that you can change the software or use pieces of it\r
+in new free programs; and that you know you can do these things.\r
+\r
+  To protect your rights, we need to make restrictions that forbid\r
+anyone to deny you these rights or to ask you to surrender the rights.\r
+These restrictions translate to certain responsibilities for you if you\r
+distribute copies of the software, or if you modify it.\r
+\r
+  For example, if you distribute copies of such a program, whether\r
+gratis or for a fee, you must give the recipients all the rights that\r
+you have.  You must make sure that they, too, receive or can get the\r
+source code.  And you must show them these terms so they know their\r
+rights.\r
+\r
+  We protect your rights with two steps: (1) copyright the software, and\r
+(2) offer you this license which gives you legal permission to copy,\r
+distribute and/or modify the software.\r
+\r
+  Also, for each author's protection and ours, we want to make certain\r
+that everyone understands that there is no warranty for this free\r
+software.  If the software is modified by someone else and passed on, we\r
+want its recipients to know that what they have is not the original, so\r
+that any problems introduced by others will not reflect on the original\r
+authors' reputations.\r
+\r
+  Finally, any free program is threatened constantly by software\r
+patents.  We wish to avoid the danger that redistributors of a free\r
+program will individually obtain patent licenses, in effect making the\r
+program proprietary.  To prevent this, we have made it clear that any\r
+patent must be licensed for everyone's free use or not licensed at all.\r
+\r
+  The precise terms and conditions for copying, distribution and\r
+modification follow.\r
+\r
+                    GNU GENERAL PUBLIC LICENSE\r
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\r
+\r
+  0. This License applies to any program or other work which contains\r
+a notice placed by the copyright holder saying it may be distributed\r
+under the terms of this General Public License.  The "Program", below,\r
+refers to any such program or work, and a "work based on the Program"\r
+means either the Program or any derivative work under copyright law:\r
+that is to say, a work containing the Program or a portion of it,\r
+either verbatim or with modifications and/or translated into another\r
+language.  (Hereinafter, translation is included without limitation in\r
+the term "modification".)  Each licensee is addressed as "you".\r
+\r
+Activities other than copying, distribution and modification are not\r
+covered by this License; they are outside its scope.  The act of\r
+running the Program is not restricted, and the output from the Program\r
+is covered only if its contents constitute a work based on the\r
+Program (independent of having been made by running the Program).\r
+Whether that is true depends on what the Program does.\r
+\r
+  1. You may copy and distribute verbatim copies of the Program's\r
+source code as you receive it, in any medium, provided that you\r
+conspicuously and appropriately publish on each copy an appropriate\r
+copyright notice and disclaimer of warranty; keep intact all the\r
+notices that refer to this License and to the absence of any warranty;\r
+and give any other recipients of the Program a copy of this License\r
+along with the Program.\r
+\r
+You may charge a fee for the physical act of transferring a copy, and\r
+you may at your option offer warranty protection in exchange for a fee.\r
+\r
+  2. You may modify your copy or copies of the Program or any portion\r
+of it, thus forming a work based on the Program, and copy and\r
+distribute such modifications or work under the terms of Section 1\r
+above, provided that you also meet all of these conditions:\r
+\r
+    a) You must cause the modified files to carry prominent notices\r
+    stating that you changed the files and the date of any change.\r
+\r
+    b) You must cause any work that you distribute or publish, that in\r
+    whole or in part contains or is derived from the Program or any\r
+    part thereof, to be licensed as a whole at no charge to all third\r
+    parties under the terms of this License.\r
+\r
+    c) If the modified program normally reads commands interactively\r
+    when run, you must cause it, when started running for such\r
+    interactive use in the most ordinary way, to print or display an\r
+    announcement including an appropriate copyright notice and a\r
+    notice that there is no warranty (or else, saying that you provide\r
+    a warranty) and that users may redistribute the program under\r
+    these conditions, and telling the user how to view a copy of this\r
+    License.  (Exception: if the Program itself is interactive but\r
+    does not normally print such an announcement, your work based on\r
+    the Program is not required to print an announcement.)\r
+\r
+These requirements apply to the modified work as a whole.  If\r
+identifiable sections of that work are not derived from the Program,\r
+and can be reasonably considered independent and separate works in\r
+themselves, then this License, and its terms, do not apply to those\r
+sections when you distribute them as separate works.  But when you\r
+distribute the same sections as part of a whole which is a work based\r
+on the Program, the distribution of the whole must be on the terms of\r
+this License, whose permissions for other licensees extend to the\r
+entire whole, and thus to each and every part regardless of who wrote it.\r
+\r
+Thus, it is not the intent of this section to claim rights or contest\r
+your rights to work written entirely by you; rather, the intent is to\r
+exercise the right to control the distribution of derivative or\r
+collective works based on the Program.\r
+\r
+In addition, mere aggregation of another work not based on the Program\r
+with the Program (or with a work based on the Program) on a volume of\r
+a storage or distribution medium does not bring the other work under\r
+the scope of this License.\r
+\r
+  3. You may copy and distribute the Program (or a work based on it,\r
+under Section 2) in object code or executable form under the terms of\r
+Sections 1 and 2 above provided that you also do one of the following:\r
+\r
+    a) Accompany it with the complete corresponding machine-readable\r
+    source code, which must be distributed under the terms of Sections\r
+    1 and 2 above on a medium customarily used for software interchange; or,\r
+\r
+    b) Accompany it with a written offer, valid for at least three\r
+    years, to give any third party, for a charge no more than your\r
+    cost of physically performing source distribution, a complete\r
+    machine-readable copy of the corresponding source code, to be\r
+    distributed under the terms of Sections 1 and 2 above on a medium\r
+    customarily used for software interchange; or,\r
+\r
+    c) Accompany it with the information you received as to the offer\r
+    to distribute corresponding source code.  (This alternative is\r
+    allowed only for noncommercial distribution and only if you\r
+    received the program in object code or executable form with such\r
+    an offer, in accord with Subsection b above.)\r
+\r
+The source code for a work means the preferred form of the work for\r
+making modifications to it.  For an executable work, complete source\r
+code means all the source code for all modules it contains, plus any\r
+associated interface definition files, plus the scripts used to\r
+control compilation and installation of the executable.  However, as a\r
+special exception, the source code distributed need not include\r
+anything that is normally distributed (in either source or binary\r
+form) with the major components (compiler, kernel, and so on) of the\r
+operating system on which the executable runs, unless that component\r
+itself accompanies the executable.\r
+\r
+If distribution of executable or object code is made by offering\r
+access to copy from a designated place, then offering equivalent\r
+access to copy the source code from the same place counts as\r
+distribution of the source code, even though third parties are not\r
+compelled to copy the source along with the object code.\r
+\r
+  4. You may not copy, modify, sublicense, or distribute the Program\r
+except as expressly provided under this License.  Any attempt\r
+otherwise to copy, modify, sublicense or distribute the Program is\r
+void, and will automatically terminate your rights under this License.\r
+However, parties who have received copies, or rights, from you under\r
+this License will not have their licenses terminated so long as such\r
+parties remain in full compliance.\r
+\r
+  5. You are not required to accept this License, since you have not\r
+signed it.  However, nothing else grants you permission to modify or\r
+distribute the Program or its derivative works.  These actions are\r
+prohibited by law if you do not accept this License.  Therefore, by\r
+modifying or distributing the Program (or any work based on the\r
+Program), you indicate your acceptance of this License to do so, and\r
+all its terms and conditions for copying, distributing or modifying\r
+the Program or works based on it.\r
+\r
+  6. Each time you redistribute the Program (or any work based on the\r
+Program), the recipient automatically receives a license from the\r
+original licensor to copy, distribute or modify the Program subject to\r
+these terms and conditions.  You may not impose any further\r
+restrictions on the recipients' exercise of the rights granted herein.\r
+You are not responsible for enforcing compliance by third parties to\r
+this License.\r
+\r
+  7. If, as a consequence of a court judgment or allegation of patent\r
+infringement or for any other reason (not limited to patent issues),\r
+conditions are imposed on you (whether by court order, agreement or\r
+otherwise) that contradict the conditions of this License, they do not\r
+excuse you from the conditions of this License.  If you cannot\r
+distribute so as to satisfy simultaneously your obligations under this\r
+License and any other pertinent obligations, then as a consequence you\r
+may not distribute the Program at all.  For example, if a patent\r
+license would not permit royalty-free redistribution of the Program by\r
+all those who receive copies directly or indirectly through you, then\r
+the only way you could satisfy both it and this License would be to\r
+refrain entirely from distribution of the Program.\r
+\r
+If any portion of this section is held invalid or unenforceable under\r
+any particular circumstance, the balance of the section is intended to\r
+apply and the section as a whole is intended to apply in other\r
+circumstances.\r
+\r
+It is not the purpose of this section to induce you to infringe any\r
+patents or other property right claims or to contest validity of any\r
+such claims; this section has the sole purpose of protecting the\r
+integrity of the free software distribution system, which is\r
+implemented by public license practices.  Many people have made\r
+generous contributions to the wide range of software distributed\r
+through that system in reliance on consistent application of that\r
+system; it is up to the author/donor to decide if he or she is willing\r
+to distribute software through any other system and a licensee cannot\r
+impose that choice.\r
+\r
+This section is intended to make thoroughly clear what is believed to\r
+be a consequence of the rest of this License.\r
+\r
+  8. If the distribution and/or use of the Program is restricted in\r
+certain countries either by patents or by copyrighted interfaces, the\r
+original copyright holder who places the Program under this License\r
+may add an explicit geographical distribution limitation excluding\r
+those countries, so that distribution is permitted only in or among\r
+countries not thus excluded.  In such case, this License incorporates\r
+the limitation as if written in the body of this License.\r
+\r
+  9. The Free Software Foundation may publish revised and/or new versions\r
+of the General Public License from time to time.  Such new versions will\r
+be similar in spirit to the present version, but may differ in detail to\r
+address new problems or concerns.\r
+\r
+Each version is given a distinguishing version number.  If the Program\r
+specifies a version number of this License which applies to it and "any\r
+later version", you have the option of following the terms and conditions\r
+either of that version or of any later version published by the Free\r
+Software Foundation.  If the Program does not specify a version number of\r
+this License, you may choose any version ever published by the Free Software\r
+Foundation.\r
+\r
+  10. If you wish to incorporate parts of the Program into other free\r
+programs whose distribution conditions are different, write to the author\r
+to ask for permission.  For software which is copyrighted by the Free\r
+Software Foundation, write to the Free Software Foundation; we sometimes\r
+make exceptions for this.  Our decision will be guided by the two goals\r
+of preserving the free status of all derivatives of our free software and\r
+of promoting the sharing and reuse of software generally.\r
+\r
+                            NO WARRANTY\r
+\r
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\r
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\r
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\r
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\r
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\r
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\r
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\r
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\r
+REPAIR OR CORRECTION.\r
+\r
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\r
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\r
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\r
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\r
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\r
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\r
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\r
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\r
+POSSIBILITY OF SUCH DAMAGES.\r
+\r
+                     END OF TERMS AND CONDITIONS\r
+\r
+            How to Apply These Terms to Your New Programs\r
+\r
+  If you develop a new program, and you want it to be of the greatest\r
+possible use to the public, the best way to achieve this is to make it\r
+free software which everyone can redistribute and change under these terms.\r
+\r
+  To do so, attach the following notices to the program.  It is safest\r
+to attach them to the start of each source file to most effectively\r
+convey the exclusion of warranty; and each file should have at least\r
+the "copyright" line and a pointer to where the full notice is found.\r
+\r
+    <one line to give the program's name and a brief idea of what it does.>\r
+    Copyright (C) <year>  <name of author>\r
+\r
+    This program is free software; you can redistribute it and/or modify\r
+    it under the terms of the GNU General Public License as published by\r
+    the Free Software Foundation; either version 2 of the License, or\r
+    (at your option) any later version.\r
+\r
+    This program is distributed in the hope that it will be useful,\r
+    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+    GNU General Public License for more details.\r
+\r
+    You should have received a copy of the GNU General Public License\r
+    along with this program; if not, write to the Free Software\r
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\r
+\r
+\r
+Also add information on how to contact you by electronic and paper mail.\r
+\r
+If the program is interactive, make it output a short notice like this\r
+when it starts in an interactive mode:\r
+\r
+    Gnomovision version 69, Copyright (C) year name of author\r
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\r
+    This is free software, and you are welcome to redistribute it\r
+    under certain conditions; type `show c' for details.\r
+\r
+The hypothetical commands `show w' and `show c' should show the appropriate\r
+parts of the General Public License.  Of course, the commands you use may\r
+be called something other than `show w' and `show c'; they could even be\r
+mouse-clicks or menu items--whatever suits your program.\r
+\r
+You should also get your employer (if you work as a programmer) or your\r
+school, if any, to sign a "copyright disclaimer" for the program, if\r
+necessary.  Here is a sample; alter the names:\r
+\r
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\r
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.\r
+\r
+  <signature of Ty Coon>, 1 April 1989\r
+  Ty Coon, President of Vice\r
+\r
+This General Public License does not permit incorporating your program into\r
+proprietary programs.  If your program is a subroutine library, you may\r
+consider it more useful to permit linking proprietary applications with the\r
+library.  If this is what you want to do, use the GNU Lesser General\r
+Public License instead of this License.\r
diff --git a/pub/qdb/readme.txt b/pub/qdb/readme.txt
new file mode 100644 (file)
index 0000000..40aab9f
--- /dev/null
@@ -0,0 +1,34 @@
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+Installation instructions can be found in the install.txt file, which came with\r
+Chirpy!. For anything else, please consult http://chirpy.sourceforge.net/.\r
+\r
+Many thanks go out to the guys over at irc.mozilla.org #bs for helping me test\r
+and giving me lots of useful feedback, to jX for hosting the irc.mozilla.org\r
+QDB, the first real Chirpy! web site, and to F2O.org for providing their free\r
+developer webspace, ideal for testing. Peace to you all.  --ct\r
+\r
+Chirpy! includes the projects listed below. Please consult their documentation,\r
+which is also supplied with Chirpy!.\r
+\r
+* ExplorerCanvas <http://excanvas.sourceforge.net/>\r
+* WebFX Slider <http://webfx.eae.net/dhtml/slider/slider.html>\r
+\r
+###############################################################################
\ No newline at end of file
diff --git a/pub/qdb/res/.htaccess b/pub/qdb/res/.htaccess
new file mode 100644 (file)
index 0000000..6f83ce8
--- /dev/null
@@ -0,0 +1,39 @@
+###############################################################################\r
+# This file adds support for compressing static text content, such as CSS and #\r
+# JavaScript files, in order to reduce traffic. To use it, you must perform   #\r
+# the following steps:                                                        #\r
+#                                                                             #\r
+# 1. Move gzip.pl from the util/ directory to the root directory. If you had  #\r
+#    to move index.cgi, e.g. because it had to be inside a cgi-bin, then move #\r
+#    gzip.pl there too.                                                       #\r
+# 2. Modify the line use constant CACHE_DIR => '...'; in gzip.pl, setting the #\r
+#    cache directory to a writable path, where it can keep compressed files.  #\r
+#    By default, this is a directory called "gzip" in your already present    #\r
+#    "cache" directory. However, the path must be relative to the directory   #\r
+#    where you puth gzip.pl!                                                  #\r
+# 3. Change gzip.pl's attributes to rwxr-xr-x (755), like index.cgi.          #\r
+# 4. Comment out the lines below by removing the # in front of them.          #\r
+# 5. If gzip.pl is not in the root directory, modify its path in the line     #\r
+#    that calls it below.                                                     #\r
+# 6. Test! Obtain a tool that allows you to view HTTP headers and look at the #\r
+#    HTTP headers for a .js or .css file inside the res/ directory. If the    #\r
+#    headers contain the line "Content-Encoding: gzip," the installation was  #\r
+#    successful. No luck? Here are some common explanations:                  #\r
+#    a. Got an Internal Server Error? Verify that you uploaded gzip.pl in     #\r
+#       ASCII mode and that you set its attributes. If you did, obtain an     #\r
+#       error log from your host and see what that tells you.                 #\r
+#    b. If the server redirected the request to the same URL, with "?nogzip"  #\r
+#       appended to it, the gzip.pl script did run, but decided compression   #\r
+#       was not possible because of an incompatibility.                       #\r
+#    c. If the server neither compressed the file, nor redirected, then       #\r
+#       gzip.pl didn't get invoked at all. The server might not support the   #\r
+#       Rewrite module, or had trouble interpreting the directives below.     #\r
+###############################################################################\r
+\r
+#<IfModule mod_rewrite.c>\r
+#RewriteEngine On\r
+#RewriteCond %{HTTP:Accept-Encoding} \bgzip\b\r
+#RewriteCond %{QUERY_STRING} =""\r
+#RewriteCond %{REQUEST_FILENAME} -s\r
+#RewriteRule \.(css|js)$ ../gzip.pl?filename=%{REQUEST_FILENAME}&uri=%{REQUEST_URI}\r
+#</IfModule>
\ No newline at end of file
diff --git a/pub/qdb/res/captcha/.htaccess b/pub/qdb/res/captcha/.htaccess
new file mode 100644 (file)
index 0000000..45552cb
--- /dev/null
@@ -0,0 +1 @@
+Options -Indexes
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/cite.ico b/pub/qdb/res/themes/default/cite.ico
new file mode 100644 (file)
index 0000000..3cba558
Binary files /dev/null and b/pub/qdb/res/themes/default/cite.ico differ
diff --git a/pub/qdb/res/themes/default/css/default.css b/pub/qdb/res/themes/default/css/default.css
new file mode 100644 (file)
index 0000000..3fcd5d6
--- /dev/null
@@ -0,0 +1,1104 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# default.css                                                                 #\r
+# The default theme, without color theme-specific style                       #\r
+###############################################################################\r
+# $Id:: default.css 294 2007-02-06 00:01:15Z ceetee                         $ #\r
+###############################################################################\r
+*/\r
+\r
+html {\r
+       font-family: Verdana, Arial, Helvetica, sans-serif;\r
+       margin: 0;\r
+       padding: 2em 5em;\r
+       background: #FBFBFB;\r
+       color: black;\r
+       font-size: 12px;\r
+}\r
+\r
+body {\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+a:link, a:visited {\r
+       text-decoration: none;\r
+       color: #555;\r
+}\r
+\r
+a:hover, a:active {\r
+       text-decoration: underline;\r
+}\r
+\r
+.news-text strong {\r
+       font-weight: bold;\r
+       font-style: normal;\r
+       text-decoration: none;\r
+}\r
+\r
+.news-text em {\r
+       font-weight: normal;\r
+       font-style: normal;\r
+       text-decoration: none;\r
+       border-bottom: 1px solid #999;\r
+}\r
+\r
+h1, h2, h3 {\r
+       font-family: Trebuchet MS, Tahoma, Verdana, Arial, Helvetica, sans-serif;\r
+       font-weight: normal;\r
+       margin: 0 0 1em 0;\r
+}\r
+\r
+h1 {\r
+       margin: 0;\r
+       padding: 0;\r
+       font-size: 275%;\r
+}\r
+\r
+h1 a, h1 a:link, h1 a:visited {\r
+       -moz-border-radius: 1em;\r
+       border-radius: 1em;\r
+       margin: 0;\r
+       padding: 0.1em 0.5em;\r
+       border-top: 0.25em solid;\r
+       border-bottom: 0.25em solid;\r
+       color: black;\r
+       text-decoration: none;\r
+       display: block;\r
+}\r
+\r
+h1 a:hover, h1 a:active {\r
+       text-decoration: underline;\r
+       color: black;\r
+}\r
+\r
+h2 {\r
+       font-size: 175%;\r
+}\r
+\r
+h3 {\r
+       font-size: 120%;\r
+}\r
+\r
+p {\r
+       margin: 0 0 1em 0;\r
+}\r
+\r
+form {\r
+       display: inline;\r
+}\r
+\r
+table {\r
+       border-collapse: collapse;\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+thead, tbody, tr, td {\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+input, select, textarea {\r
+       border: 1px solid #CCC;\r
+       background: white;\r
+       color: #666;\r
+       margin: 0.1em;\r
+       padding: 0.25em;\r
+       -moz-border-radius: 0.5em;\r
+       border-radius: 0.5em;\r
+}\r
+\r
+select option {\r
+       padding: 0.1em;\r
+       margin: 0.1em;\r
+       background: #F9F9F9;\r
+}\r
+\r
+input[type=radio], input[type=checkbox] {\r
+       border: none;\r
+       vertical-align: middle;\r
+}\r
+\r
+input, select {\r
+       font-family: Verdana, Arial, Helvetica, sans-serif;\r
+       font-size: 12px;\r
+}\r
+\r
+textarea {\r
+       font-family: Consolas, Monaco, Courier New, monospace;\r
+       font-size: 12px;\r
+       line-height: 1.5em;\r
+}\r
+\r
+input:focus, select:focus, textarea:focus {\r
+       color: black;\r
+       border-color: #999;\r
+}\r
+\r
+input[type=submit], input[type=reset], input[type=button] {\r
+       padding-left: 1em;\r
+       padding-right: 1em;\r
+}\r
+\r
+input[type=submit]:hover, input[type=reset]:hover, input[type=button]:hover {\r
+       color: black;\r
+       cursor: pointer;\r
+}\r
+\r
+label {\r
+       border-bottom: 1px dotted transparent;\r
+}\r
+\r
+label:hover {\r
+       border-color: #CCC;\r
+       cursor: pointer;\r
+}\r
+\r
+ul#navigation {\r
+       -moz-border-radius: 0 0 1em 1em;\r
+       border-radius: 0 0 1em 1em;\r
+       margin: 0 4em 3em 0;\r
+       padding: 0.2em 0;\r
+       list-style-type: none;\r
+       font-size: 90%;\r
+       text-align: right;\r
+       text-transform: uppercase;\r
+       white-space: nowrap;\r
+       letter-spacing: 0.1em;\r
+}\r
+\r
+ul#navigation li {\r
+       display: inline;\r
+       margin: 0;\r
+       padding: 0 0 0 0.5em;\r
+}\r
+\r
+ul#navigation li a, ul#navigation li a:visited {\r
+       -moz-border-radius: 0 0 1em 1em;\r
+       border-radius: 0 0 1em 1em;\r
+       background: #EEE;\r
+       margin: 0;\r
+       padding: 0.2em 1em 0.3em 1em;\r
+       color: black;\r
+       text-decoration: none;\r
+       border-bottom: 0.2em solid #E0E0E0;\r
+}\r
+\r
+#footer {\r
+       clear: both;\r
+       float: right;\r
+       font-size: 75%;\r
+       text-align: right;\r
+       border-top: 1px solid black;\r
+       margin: 2.5em 0 0 0;\r
+       padding: 0.75em 1em 0 10em;\r
+       line-height: 1.75em;\r
+}\r
+\r
+#content {\r
+       padding: 1.5em 2.5em;\r
+       -moz-border-radius: 2.5em;\r
+       border-radius: 2.5em;\r
+       background: white;\r
+       min-height: 15em;\r
+       line-height: 1.75em;\r
+}\r
+\r
+ul.quote-list {\r
+       list-style-type: none;\r
+       margin: 0 0 1em 0;\r
+       padding: 0;\r
+}\r
+\r
+ul.quote-list li {\r
+       margin: 0 0 1em 0;\r
+       padding: 0;\r
+}\r
+\r
+ul.quote-list .quote-container {\r
+       background: #FCFCFC;\r
+       -moz-border-radius: 1em 1em 2em 0.5em;\r
+       border-radius: 1em 1em 2em 0.5em;\r
+}\r
+\r
+ul.quote-list li h3.quote-header {\r
+       margin: 0;\r
+       padding: 0.25em 0.75em;\r
+       border: 1px #F4F4F4 solid;\r
+       background: #F7F7F7;\r
+       -moz-border-radius: 0.5em 0.5em 0 0;\r
+       border-radius: 0.5em 0.5em 0 0;\r
+}\r
+\r
+ul.quote-list li h3.quote-header * {\r
+       margin: 0 0.125em;\r
+       padding: 0;\r
+}\r
+\r
+ul.quote-list li h3.quote-header * * {\r
+       margin: 0;\r
+}\r
+\r
+ul.quote-list li h3.quote-header a.casted-vote {\r
+       opacity: 0.2;\r
+       text-decoration: line-through;\r
+       cursor: default;\r
+}\r
+\r
+/* opacity doesn't work in IE, so hide it altogether */\r
+* html ul.quote-list li h3.quote-header a.casted-vote,\r
+*:first-child + html ul.quote-list li h3.quote-header a.casted-vote {\r
+       visibility: hidden;\r
+}\r
+\r
+blockquote.quote-body {\r
+       margin: 0.5em 0.75em;\r
+       padding: 0;\r
+       font-family: Consolas, Monaco, Courier New, monospace;\r
+       font-size: 12px;\r
+}\r
+\r
+blockquote.quote-body p {\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+#confirmation-form blockquote.quote-body,\r
+#quote-removal-confirmation-form blockquote.quote-body {\r
+       margin: 1em 0;\r
+       padding: 1em;\r
+       border: 1px dashed #EEE;\r
+}\r
+\r
+ul.quote-list li .quote-footer {\r
+       margin: 0.25em 0.75em;\r
+       padding: 0;\r
+       border-top: 1px dashed #EEE;\r
+       line-height: 1.5em;\r
+}\r
+\r
+ul.quote-list li .quote-footer, ul.quote-list li .quote-footer a {\r
+       color: #999;\r
+}\r
+\r
+ul.quote-list li .quote-footer p {\r
+       font-size: 80%;\r
+       margin: 0;\r
+       padding: 0 0 0 2.5ex;\r
+       text-indent: -2.5ex;\r
+}\r
+\r
+ul.quote-list li .quote-notes .quote-notes-title,\r
+ul.quote-list li .quote-tags .quote-tags-title {\r
+       font-size: 80%;\r
+       text-transform: uppercase;\r
+       font-style: normal;\r
+       font-weight: normal;\r
+}\r
+\r
+ul.quote-list h3.quote-header .quote-date,\r
+ul.quote-list h3.quote-header .quote-edit,\r
+ul.quote-list h3.quote-header .quote-remove,\r
+ul.quote-list h3.quote-header .quote-unflag,\r
+ul.quote-list h3.quote-header .quote-live-vote-result {\r
+       font-size: 80%;\r
+}\r
+\r
+ul.quote-list h3.quote-header .quote-vote-count {\r
+       margin-left: 0;\r
+       font-size: 90%;\r
+}\r
+\r
+ul.quote-list h3.quote-header .quote-live-vote-result {\r
+       float: right;\r
+       text-transform: uppercase;\r
+}\r
+\r
+ul.quote-list li.flagged h3.quote-header {\r
+       border: 1px #E9E9E9 solid;\r
+       background: #ECECEC;\r
+}\r
+\r
+ul.quote-list li.unapproved h3.quote-header {\r
+       border: 1px #F9E9E9 solid;\r
+       background: #FCF0F0;\r
+}\r
+\r
+.flag-options label, .approval-options label {\r
+       margin-right: 0.25em;\r
+}\r
+\r
+#welcome-message-container {\r
+       width: 50%;\r
+       float: left;\r
+}\r
+\r
+#welcome-message {\r
+       padding-right: 1.25em;\r
+}\r
+\r
+#news-display-container {\r
+       width: 50%;\r
+       float: right;\r
+}\r
+\r
+#news-display {\r
+       padding-left: 1.25em;\r
+}\r
+\r
+#start-page-end {\r
+       clear: both;\r
+}\r
+\r
+ul#news-list {\r
+       list-style-type: none;\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+ul#news-list .news-text {\r
+       margin-bottom: 0.5em;\r
+}\r
+\r
+ul#news-list .news-footer {\r
+       font-weight: bold;\r
+       margin-bottom: 1.5em;\r
+}\r
+\r
+ul#news-list .news-footer .news-edit, ul#news-list .news-footer .news-remove {\r
+       font-size: 80%;\r
+}\r
+\r
+.quote-browser {\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+#quote-browser-top {\r
+       float: right;\r
+       margin-top: 0.25em;\r
+}\r
+\r
+#quote-browser-bottom {\r
+       text-align: right;\r
+       margin-bottom: 0.25em;\r
+}\r
+\r
+.quote-browser * {\r
+       margin: 0 0 0 0.1em;\r
+       padding: 0.25em 0.5em;\r
+       background: #F9F9F9;\r
+       -moz-border-radius: 0 0.5em 2em 0.5em;\r
+       border-radius: 0 0.5em 2em 0.5em;\r
+}\r
+\r
+.quote-browser .inactive {\r
+       color: #C9C9C9;\r
+}\r
+\r
+#search-haiku {\r
+       font-family: Trebuchet MS, Tahoma, Verdana, Arial, Helvetica, sans-serif;\r
+       font-style: italic;\r
+       font-size: 14px;\r
+       color: #DDD;\r
+       letter-spacing: 0.5ex;\r
+       line-height: 2.25em;\r
+       text-align: right;\r
+       margin: -3em 0 1em 0;\r
+       padding: 0 2em;\r
+       cursor: default;\r
+}\r
+\r
+#search-haiku del {\r
+       display: none;\r
+}\r
+\r
+#search-haiku:hover {\r
+       letter-spacing: 0.4ex;\r
+}\r
+\r
+/* IE hates cool CSS */\r
+* html #search-haiku, *:first-child + html #search-haiku {\r
+       display: none;\r
+}\r
+\r
+#search-haiku:hover del {\r
+       display: inline;\r
+       text-decoration: none;\r
+       color: #AAA;\r
+}\r
+\r
+#search-form {\r
+       white-space: nowrap;\r
+       margin-bottom: 1.5em;\r
+}\r
+\r
+#search-form #query-container, #search-form #submit-container {\r
+       display: inline;\r
+}\r
+\r
+#search-form #query-field {\r
+       width: 33%;\r
+}\r
+\r
+#submit-form #quote-field, #edit-quote-form #quote-field, .quote-data .body-field,\r
+#submit-form #notes-field, #edit-quote-form #notes-field, .quote-data .notes-field,\r
+#edit-news-item-form #news-item-field {\r
+       width: 95%;\r
+       height: 12em;\r
+       display: block;\r
+}\r
+\r
+#submit-form #notes-field, #edit-quote-form #notes-field, .quote-data .notes-field {\r
+       height: 6em;\r
+}\r
+\r
+.quote-data .field-container {\r
+       margin: 0.5em 0.75em;\r
+}\r
+\r
+#submit-form #quote-container, #submit-form #notes-container,\r
+#submit-form #tags-container, #submit-form #captcha-container,\r
+#edit-quote-form #quote-container, #edit-quote-form #notes-container,\r
+#edit-quote-form #tags-container {\r
+       margin-bottom: 1em;\r
+}\r
+\r
+#submit-form #tags-container label,\r
+#edit-quote-form #tags-container label {\r
+       display: block;\r
+}\r
+\r
+#submit-form #tags-container input,\r
+#edit-quote-form #tags-container input,\r
+.quote-data input {\r
+       width: 95%;\r
+}\r
+\r
+#submit-form #captcha-image {\r
+       vertical-align: middle;\r
+       padding: 0;\r
+       margin: 0.1em 1ex 0.1em 0.1em;\r
+}\r
+\r
+#submit-form #captcha-code-field {\r
+       width: 16ex;\r
+}\r
+\r
+#jump-to-quote-form #jump-to-id-field {\r
+       width: 8ex;\r
+}\r
+\r
+#jump-to-quote-form #jump-to-quote-submit-button {\r
+       padding: 0.1em 0.5em;\r
+}\r
+\r
+ul.tag-cloud {\r
+       list-style: none;\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+ul.tag-cloud li {\r
+       display: inline;\r
+       margin: 0;\r
+       padding: 0 1ex 0 0;\r
+}\r
+\r
+ul.tag-cloud li a {\r
+       white-space: nowrap;\r
+}\r
+\r
+#confirmation-form #confirm-button, #confirmation-form #cancel-button {\r
+       width: 16ex;\r
+}\r
+\r
+ul.tabbed-pane-header {\r
+       list-style-type: none;\r
+       margin: 0 0 0 1.5em;\r
+       padding: 0;\r
+       line-height: 2em;\r
+       white-space: nowrap;\r
+}\r
+\r
+ul.tabbed-pane-header li {\r
+       display: inline;\r
+       margin: 0 0.25em 0 0;\r
+       padding: 0;\r
+}\r
+\r
+ul.tabbed-pane-header li a, ul.tabbed-pane-header li a:visited {\r
+       padding: 0.25em 0.75em 0.5em 0.75em;\r
+       background: #F0F0F0;\r
+       border-top: 0.2em solid #ECECEC;\r
+       color: black;\r
+       text-decoration: none;\r
+       -moz-border-radius: 0.5em 0.5em 0 0;\r
+       border-radius: 0.5em 0.5em 0 0;\r
+       cursor: pointer;\r
+}\r
+\r
+ul.tabbed-pane-contents {\r
+       list-style-type: none;\r
+       margin: 0;\r
+       padding: 1em;\r
+       -moz-border-radius: 1em;\r
+       border-radius: 1em;\r
+}\r
+\r
+ul.tabbed-pane-contents > li {\r
+       display: block;\r
+       margin: 0;\r
+       padding: 0;\r
+       min-height: 15em;\r
+}\r
+\r
+.tabbed-pane-contents .tab-title {\r
+       display: none;\r
+}\r
+\r
+#post-news-form {\r
+       margin-bottom: 1em;\r
+}\r
+\r
+#post-news-form #news-field {\r
+       width: 95%;\r
+       height: 12em;\r
+       display: block;\r
+}\r
+\r
+#quick-manage-quote-form #quick-manage-quote-id-container,\r
+#quick-manage-quote-form #quick-manage-quote-options,\r
+#quick-manage-quote-form #quick-manage-quote-submit {\r
+       display: inline;\r
+       margin-right: 0.5em;\r
+}\r
+\r
+#quick-manage-quote-form #quick-manage-quote-id-field {\r
+       width: 8ex;\r
+}\r
+\r
+#account-manager {\r
+       text-align: right;\r
+       padding: 0 1em;\r
+}\r
+\r
+#account-manager #username-select-container {\r
+       width: 40%;\r
+       float: left;\r
+}\r
+\r
+#account-manager #username-select {\r
+       width: 100%;\r
+       height: 20em;\r
+}\r
+\r
+#account-manager #username-select #username-new-user {\r
+       font-weight: bold;\r
+}\r
+\r
+#account-manager #username-select .user-level-9,\r
+#account-manager #level-select .user-level-9,\r
+#edit-news-item-form #news-item-poster-select .user-level-9 {\r
+       background: #FFF0F9;\r
+}\r
+\r
+#account-manager #username-select .user-level-6,\r
+#account-manager #level-select .user-level-6,\r
+#edit-news-item-form #news-item-poster-select .user-level-6  {\r
+       background: #FFF9F0;\r
+}\r
+\r
+#account-manager #username-select .user-level-3,\r
+#account-manager #level-select .user-level-3,\r
+#edit-news-item-form #news-item-poster-select .user-level-3  {\r
+       background: #F0FFE9;\r
+}\r
+\r
+#account-manager #username-field {\r
+       margin-top: 2em;\r
+}\r
+\r
+#account-manager #username-field,\r
+#account-manager #password-field,\r
+#account-manager #repeat-password-field {\r
+       width: 40%;\r
+}\r
+\r
+#account-manager #username-select-container,\r
+#account-manager #username-container,\r
+#account-manager #repeat-password-container,\r
+#account-manager #level-container {\r
+       margin-bottom: 0.75em;\r
+}\r
+\r
+#account-manager #account-manager-result {\r
+       margin: 2em 0 0 0;\r
+       padding: 0.25em 0 0 0;\r
+       width: 45%;\r
+       float: right;\r
+       font-weight: bold;\r
+       border-top: 1px solid #EEE;\r
+}\r
+\r
+#edit-news-item-form #news-item-poster-container,\r
+#edit-news-item-form #edit-news-item-submit-container {\r
+       display: inline;\r
+}\r
+\r
+#post-news-form #news-container, #edit-news-item-form #news-item-container {\r
+       margin-bottom: 0.75em;\r
+}\r
+\r
+#login-form #user-name-container,\r
+#login-form #password-container,\r
+#login-form #submit-container,\r
+#change-password-form #current-password-container,\r
+#change-password-form #new-password-container,\r
+#change-password-form #repeat-new-password-container,\r
+#change-password-form #submit-container {\r
+       display: inline;\r
+       margin-right: 0.5em;\r
+       white-space: nowrap;\r
+}\r
+\r
+#login-form #user-name-field,\r
+#login-form #password-field {\r
+       width: 25ex;\r
+}\r
+\r
+#change-password-form #current-password-field,\r
+#change-password-form #new-password-field,\r
+#change-password-form #repeat-new-password-field {\r
+       width: 20ex;\r
+}\r
+\r
+#mass-approve-container, #mass-unflag-container {\r
+       display: inline;\r
+}\r
+\r
+#update-information {\r
+       background: #FFF6E6;\r
+       border: 2px dashed #F90;\r
+       color: #E70;\r
+       margin: 2em 5%;\r
+       padding: 0;\r
+       font-weight: bold;\r
+       line-height: 1.25em;\r
+}\r
+\r
+#update-information a, #update-information a:visited {\r
+       display: block;\r
+       color: #E70;\r
+       text-align: center;\r
+       margin: 0;\r
+       padding: 1em;\r
+}\r
+\r
+#update-information a:hover {\r
+       text-decoration: none;\r
+       color: #E50;\r
+}\r
+\r
+#update-information p {\r
+       text-align: center;\r
+       margin: 0.75em 1em 0.25em 1em;\r
+       padding: 0;\r
+}\r
+\r
+#update-information blockquote {\r
+       text-align: center;\r
+       margin: 0 1em 0.75em 1em;\r
+       color: #666;\r
+       font-weight: normal;\r
+       font-style: italic;\r
+}\r
+\r
+#event-log-table {\r
+       border-collapse: collapse;\r
+       width: 100%;\r
+       clear: both;\r
+}\r
+\r
+.event-log-navigation {\r
+       text-align: center;\r
+}\r
+\r
+#event-log-navigation-top {\r
+       margin-bottom: 0.75em;\r
+}\r
+\r
+#event-log-navigation-bottom {\r
+       margin-top: 0.75em;\r
+}\r
+\r
+.event-log-navigation * {\r
+       padding: 0.25em 0.5em;\r
+       background: #F9F9F9;\r
+       -moz-border-radius: 0 0.5em 2em 0.5em;\r
+       border-radius: 0 0.5em 2em 0.5em;\r
+       cursor: pointer;\r
+}\r
+\r
+.event-log-navigation .back {\r
+       float: left;\r
+       padding-top: 0;\r
+       padding-bottom: 0;\r
+}\r
+\r
+.event-log-navigation .forward {\r
+       float: right;\r
+       padding-top: 0;\r
+       padding-bottom: 0;\r
+}\r
+\r
+.event-log-navigation .inactive {\r
+       color: #C9C9C9;\r
+       cursor: default;\r
+       text-decoration: none;\r
+}\r
+\r
+#event-log-table tr {\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+#event-log-table th, #event-log-table td {\r
+       margin: 0;\r
+       padding: 0.25ex 1ex;\r
+       text-align: left;\r
+       vertical-align: top;\r
+}\r
+\r
+#event-log-table thead tr {\r
+       background: #F0F0F0;\r
+       border: 1px solid #E6E6E6;\r
+}\r
+\r
+#event-log-table td {\r
+       border: 1px solid #F0F0F0;\r
+}\r
+\r
+#event-log-table tbody {\r
+       border: 1px solid #E6E6E6;\r
+}\r
+\r
+#event-log-table tr.odd {\r
+       background: #F9F9F9;\r
+}\r
+\r
+#event-log-table tr.even {\r
+       background: white;\r
+}\r
+\r
+#event-log-table td.id {\r
+       text-align: right;\r
+}\r
+\r
+#event-log-table th.id {\r
+       text-align: center;\r
+}\r
+\r
+#event-log-table th.id a {\r
+       display: block;\r
+       text-decoration: none;\r
+       color: black;\r
+}\r
+\r
+#event-log-table th, #event-log-table td.date, #event-log-table td.event {\r
+       white-space: nowrap;\r
+}\r
+\r
+#event-log-table th.event, #event-log-table td.event {\r
+       width: 100%;\r
+}\r
+\r
+#event-log-table .guest {\r
+       font-style: italic;\r
+}\r
+\r
+#event-log-table .removed-account {\r
+       font-style: italic;\r
+}\r
+\r
+#event-log-table .filtered, #event-log-table .filtered * {\r
+       text-decoration: underline;\r
+}\r
+\r
+#event-log-table .empty {\r
+       font-style: italic;\r
+}\r
+\r
+#event-log-table .loading {\r
+       font-style: italic;\r
+       text-align: center;\r
+}\r
+\r
+.statistics-section {\r
+       display: inline;\r
+       float: left;\r
+       margin-right: 1.5em;\r
+}\r
+\r
+.statistics-section h3 {\r
+       margin-bottom: 0.75em;\r
+}\r
+\r
+#after-statistics {\r
+       clear: both;\r
+}\r
+\r
+.chart, .chart * {\r
+       cursor: default;\r
+}\r
+\r
+.chart {\r
+       position: relative;\r
+       width: 720px;\r
+       height: 400px;\r
+       overflow: hidden;\r
+       border: 1px solid #CCC;\r
+       line-height: 1em;\r
+       font-size: 10px;\r
+       color: #666;\r
+       background: #FCFCFC;\r
+       margin: 0 0 1.5em 0;\r
+}\r
+\r
+.bar-chart-graph, .ogive-graph {\r
+       position: absolute;\r
+       left: 50px;\r
+       top: 10px;\r
+       width: 660px;\r
+       height: 360px;\r
+       border-bottom: 1px solid #666;\r
+       border-left: 1px solid #666;\r
+}\r
+\r
+.chart-line {\r
+       position: absolute;\r
+       left: 0;\r
+       width: 100%;\r
+       height: 0;\r
+       border-top: 1px dashed #E0E0E0;\r
+}\r
+\r
+.chart-labels {\r
+       position: absolute;\r
+       left: 50px;\r
+       bottom: 10px;\r
+       width: 660px;\r
+       margin-left: 1px;\r
+}\r
+\r
+.chart-values {\r
+       position: absolute;\r
+       left: 10px;\r
+       top: 10px;\r
+       width: 30px;\r
+       height: 360px;\r
+}\r
+\r
+.chart-value {\r
+       position: absolute;\r
+       right: 0;\r
+       text-align: right;\r
+       margin-bottom: -5px;\r
+}\r
+\r
+.chart-label {\r
+       position: absolute;\r
+       bottom: 0;\r
+}\r
+\r
+.chart-inner-label {\r
+       position: absolute;\r
+       left: 0;\r
+       bottom: 0;\r
+       width: 100%;\r
+}\r
+\r
+.bar-chart-column {\r
+       position: absolute;\r
+       bottom: 0;\r
+       height: 100%;\r
+}\r
+\r
+.bar-chart-bar {\r
+       position: absolute;\r
+       left: 0;\r
+       bottom: 0;\r
+       width: 100%;\r
+}\r
+\r
+.bar-chart-inner-bar {\r
+       margin: 0 5%;\r
+       min-width: 1px;\r
+       height: 100%;\r
+       background: #DCDCDC;\r
+       -moz-border-radius: 20% 20% 0 0;\r
+       border-radius: 20% 20% 0 0;\r
+}\r
+\r
+.bar-chart-column:hover .bar-chart-inner-bar {\r
+       background: #D6D6D6;\r
+}\r
+\r
+.bar-chart-average-container {\r
+       position: absolute;\r
+       left: 0;\r
+       width: 100%;\r
+       height: 0;\r
+       border-top: 1px solid #BCBCBC;\r
+       color: #999;\r
+}\r
+\r
+.bar-chart-average-container div {\r
+       float: right;\r
+       padding: 2px 5px;\r
+}\r
+\r
+.pie-chart-graph {\r
+       position: absolute;\r
+       left: 20px;\r
+       top: 20px;\r
+}\r
+\r
+.pie-chart-legend {\r
+       position: absolute;\r
+       right: 20px;\r
+       top: 70px;\r
+       border: 1px solid #ACACAC;\r
+       background: #FEFEFE;\r
+       margin: 0;\r
+       padding: 8px 10px;\r
+       width: 280px;\r
+       white-space: nowrap;\r
+}\r
+\r
+.pie-chart-legend dt, .pie-chart-legend dd {\r
+       margin: 0;\r
+       padding: 0;\r
+}\r
+\r
+.pie-chart-legend dt {\r
+       float: left;\r
+       width: 21px;\r
+       height: 20px;\r
+}\r
+\r
+.pie-chart-legend dt div {\r
+       width: 14px;\r
+       height: 14px;\r
+       border: 1px solid #ACACAC;\r
+       margin: 2px 0 0 0;\r
+}\r
+\r
+.pie-chart-legend dd {\r
+       height: 15px;\r
+       padding: 5px 0 0 0;\r
+       text-align: right;\r
+}\r
+\r
+.pie-chart-legend dd .label {\r
+       float: left;\r
+       text-align: left;\r
+       width: 180px;\r
+}\r
+\r
+.pie-chart-legend dd .value, .pie-chart-legend dd .percentage {\r
+       color: #999;\r
+       width: 35px;\r
+}\r
+\r
+.pie-chart-legend dd .value {\r
+       float: left;\r
+       text-align: right;\r
+}\r
+\r
+.pie-chart-legend dd .percentage {\r
+       float: right;\r
+}\r
+\r
+.ogive .regression-equation {\r
+       color: #999;\r
+}\r
+\r
+#tag-cloud-slider-container {\r
+       float: right;\r
+       background: #FBFBFB;\r
+       padding: 1ex;\r
+}\r
+\r
+#tag-usage-minimum {\r
+       float: left;\r
+       margin: 0 1ex 0 0;\r
+       font-style: italic;\r
+}\r
+\r
+#tag-cloud-slider-container .dynamic-slider-control {\r
+       float: left;\r
+}\r
+\r
+.dynamic-slider-control {\r
+       position: relative;\r
+       -moz-user-focus: normal;\r
+       -moz-user-select: none;\r
+       cursor: default;\r
+       width: 150px;\r
+       height: 1.75em;\r
+}\r
+\r
+.dynamic-slider-control * {\r
+       font-size: 1px;\r
+}\r
+\r
+.dynamic-slider-control input {\r
+       display: none;\r
+}\r
+\r
+.dynamic-slider-control .handle {\r
+       position: absolute;\r
+       -moz-user-select: none;\r
+       cursor: default;\r
+       background: #D3D3D3;\r
+       border: 1px solid;\r
+       border-color: #F3F3F3 #B3B3B3 #B3B3B3 #F3F3F3;\r
+       width: 5px;\r
+}\r
+\r
+.dynamic-slider-control .line {\r
+       behavior: url("../js/slider/boxsizing.htc");\r
+       box-sizing: content-box;\r
+       -moz-box-sizing: content-box;\r
+       position: absolute;\r
+       overflow: hidden;\r
+       border: 1px solid;\r
+       border-color: #A3A3A3 #E3E3E3 #E3E3E3 #A3A3A3;\r
+       background: #C3C3C3;\r
+       height: 1px;\r
+}\r
+\r
+.dynamic-slider-control .line div {\r
+       display: none;\r
+}
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/css/styles/default.css b/pub/qdb/res/themes/default/css/styles/default.css
new file mode 100644 (file)
index 0000000..2d652af
--- /dev/null
@@ -0,0 +1,66 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# default.css                                                                 #\r
+# "Default" color theme for the default theme                                 #\r
+###############################################################################\r
+# $Id:: default.css 291 2007-02-05 21:24:46Z ceetee                         $ #\r
+###############################################################################\r
+*/\r
+\r
+a:hover, a:active {\r
+       color: #F63;\r
+}\r
+\r
+h1 a, h1 a:link, h1 a:visited {\r
+       background: #CFB;\r
+       border-top-color: #D6FFC6;\r
+       border-bottom-color: #C9F9B9;\r
+}\r
+\r
+ul#navigation li a:hover, ul#navigation li a:active,\r
+ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {\r
+       background: #FDA;\r
+       border-color: #F0D0A0;\r
+}\r
+\r
+ul.quote-list li:hover h3.quote-header {\r
+       background: #FEC;\r
+       border-color: #FFE9C9;\r
+}\r
+\r
+ul.quote-list li:hover .quote-container {\r
+       background: #FFFCEC;\r
+}\r
+\r
+ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {\r
+       color: #BA9;\r
+       border-color: #FFE9C9;\r
+}\r
+\r
+ul.tabbed-pane-header li a.active-tab {\r
+       background: #ECF6EC;\r
+       border-top-color: #C0F0B0;\r
+}\r
+\r
+ul.tabbed-pane-contents {\r
+       border: 1px solid #ECF6EC;\r
+}
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/css/styles/fish_tank.css b/pub/qdb/res/themes/default/css/styles/fish_tank.css
new file mode 100644 (file)
index 0000000..34c034a
--- /dev/null
@@ -0,0 +1,66 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# fish_tank.css                                                               #\r
+# "Fish Tank" color theme for the default theme                               #\r
+###############################################################################\r
+# $Id:: fish_tank.css 291 2007-02-05 21:24:46Z ceetee                       $ #\r
+###############################################################################\r
+*/\r
+\r
+a:hover, a:active {\r
+       color: #3F6;\r
+}\r
+\r
+h1 a, h1 a:link, h1 a:visited {\r
+       background: #BCF;\r
+       border-top-color: #C6D6FF;\r
+       border-bottom-color: #B9C9F9;\r
+}\r
+\r
+ul#navigation li a:hover, ul#navigation li a:active,\r
+ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {\r
+       background: #AFD;\r
+       border-color: #A0F0D0;\r
+}\r
+\r
+ul.quote-list li:hover h3.quote-header {\r
+       background: #CFE;\r
+       border-color: #C9FFE9;\r
+}\r
+\r
+ul.quote-list li:hover .quote-container {\r
+       background: #ECFFFC;\r
+}\r
+\r
+ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {\r
+       color: #9BA;\r
+       border-color: #C9FFE9;\r
+}\r
+\r
+ul.tabbed-pane-header li a.active-tab {\r
+       background: #ECECF6;\r
+       border-top-color: #B0C0F0;\r
+}\r
+\r
+ul.tabbed-pane-contents {\r
+       border: 1px solid #ECECF6;\r
+}
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/css/styles/grayscale.css b/pub/qdb/res/themes/default/css/styles/grayscale.css
new file mode 100644 (file)
index 0000000..800cc08
--- /dev/null
@@ -0,0 +1,66 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# grayscale.css                                                               #\r
+# "Grayscale" color theme for the default theme                               #\r
+###############################################################################\r
+# $Id:: grayscale.css 291 2007-02-05 21:24:46Z ceetee                       $ #\r
+###############################################################################\r
+*/\r
+\r
+a:hover, a:active {\r
+       color: #999;\r
+}\r
+\r
+h1 a, h1 a:link, h1 a:visited {\r
+       background: #ECECEC;\r
+       border-top-color: #F3F3F3;\r
+       border-bottom-color: #E9E9E9;\r
+}\r
+\r
+ul#navigation li a:hover, ul#navigation li a:active,\r
+ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {\r
+       background: #F3F3F3;\r
+       border-color: #ECECEC;\r
+}\r
+\r
+ul.quote-list li:hover h3.quote-header {\r
+       background: #F6F6F6;\r
+       border-color: #F0F0F0;\r
+}\r
+\r
+ul.quote-list li:hover .quote-container {\r
+       background: #FCFCFC;\r
+}\r
+\r
+ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {\r
+       color: #BBB;\r
+       border-color: #F0F0F0;\r
+}\r
+\r
+ul.tabbed-pane-header li a.active-tab {\r
+       background: #E9E9E9;\r
+       border-top-color: #CCC;\r
+}\r
+\r
+ul.tabbed-pane-contents {\r
+       border: 1px solid #E9E9E9;\r
+}
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/css/styles/nineties.css b/pub/qdb/res/themes/default/css/styles/nineties.css
new file mode 100644 (file)
index 0000000..89d4810
--- /dev/null
@@ -0,0 +1,66 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# nineties.css                                                                #\r
+# "Nineties" color theme for the default theme                                #\r
+###############################################################################\r
+# $Id:: nineties.css 291 2007-02-05 21:24:46Z ceetee                        $ #\r
+###############################################################################\r
+*/\r
+\r
+a:hover, a:active {\r
+       color: #69C;\r
+}\r
+\r
+h1 a, h1 a:link, h1 a:visited {\r
+       background: #CEF;\r
+       border-top-color: #D6F6FF;\r
+       border-bottom-color: #C9E9F9;\r
+}\r
+\r
+ul#navigation li a:hover, ul#navigation li a:active,\r
+ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {\r
+       background: #FDA;\r
+       border-color: #F0D0A0;\r
+}\r
+\r
+ul.quote-list li:hover h3.quote-header {\r
+       background: #CEF;\r
+       border-color: #C9E9FF;\r
+}\r
+\r
+ul.quote-list li:hover .quote-container {\r
+       background: #ECFCFF;\r
+}\r
+\r
+ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {\r
+       color: #9AB;\r
+       border-color: #C9E9FF;\r
+}\r
+\r
+ul.tabbed-pane-header li a.active-tab {\r
+       background: #E9E9E9;\r
+       border-top-color: #CCC;\r
+}\r
+\r
+ul.tabbed-pane-contents {\r
+       border: 1px solid #E9E9E9;\r
+}
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/css/styles/spring.css b/pub/qdb/res/themes/default/css/styles/spring.css
new file mode 100644 (file)
index 0000000..8f9e174
--- /dev/null
@@ -0,0 +1,66 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# spring.css                                                                  #\r
+# "Spring" color theme for the default theme                                  #\r
+###############################################################################\r
+# $Id:: spring.css 291 2007-02-05 21:24:46Z ceetee                          $ #\r
+###############################################################################\r
+*/\r
+\r
+a:hover, a:active {\r
+       color: #36F;\r
+}\r
+\r
+h1 a, h1 a:link, h1 a:visited {\r
+       background: #BFC;\r
+       border-top-color: #C6FFD6;\r
+       border-bottom-color: #B9F9C9;\r
+}\r
+\r
+ul#navigation li a:hover, ul#navigation li a:active,\r
+ul.tabbed-pane-header li a:hover, ul.tabbed-pane-header li a:active {\r
+       background: #ADF;\r
+       border-color: #A0D0F0;\r
+}\r
+\r
+ul.quote-list li:hover h3.quote-header {\r
+       background: #CEF;\r
+       border-color: #C9E9FF;\r
+}\r
+\r
+ul.quote-list li:hover .quote-container {\r
+       background: #ECFCFF;\r
+}\r
+\r
+ul.quote-list li:hover .quote-footer, ul.quote-list li:hover .quote-tags a {\r
+       color: #9AB;\r
+       border-color: #C9E9FF;\r
+}\r
+\r
+ul.tabbed-pane-header li a.active-tab {\r
+       background: #ECF6EC;\r
+       border-top-color: #B0F0C0;\r
+}\r
+\r
+ul.tabbed-pane-contents {\r
+       border: 1px solid #ECF6EC;\r
+}
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/administration.js b/pub/qdb/res/themes/default/js/administration.js
new file mode 100644 (file)
index 0000000..00220c4
--- /dev/null
@@ -0,0 +1,483 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# administration.js                                                           #\r
+# Functions specific to the administration interface                          #\r
+###############################################################################\r
+# $Id:: administration.js 291 2007-02-05 21:24:46Z ceetee                   $ #\r
+###############################################################################\r
+*/\r
+\r
+var eventLogTable;\r
+var eventLogTableBody;\r
+var eventLogHeaders;\r
+var eventLogRequest;\r
+var eventLogPreviousLinks = new Array();\r
+var eventLogNextLinks = new Array();\r
+var eventLogCurrentLinks = new Array();\r
+var eventLogURLParam = new Array();\r
+eventLogURLParam["start"] = 0;\r
+eventLogURLParam["count"] = 10;\r
+eventLogURLParam["asc"] = 0;\r
+\r
+function editQuote (id) {\r
+       var editLink = document.getElementById("quote-edit-" + id);\r
+       editLink.parentNode.removeChild(editLink);\r
+       var body = getNodeText("quote-body-" + id);\r
+       var notes = getNodeText("quote-notes-" + id);\r
+       var tags = getNodeText("quote-tags-" + id);\r
+       var dataNode = document.getElementById("quote-data-" + id);\r
+       while (dataNode.firstChild) dataNode.removeChild(dataNode.firstChild);\r
+       var bodyArea = document.createElement("textarea");\r
+       bodyArea.name = "body_" + id;\r
+       bodyArea.appendChild(document.createTextNode(body));\r
+       bodyArea.className = "body-field";\r
+       var notesArea = document.createElement("textarea");\r
+       notesArea.name = "notes_" + id;\r
+       notesArea.appendChild(document.createTextNode(notes));\r
+       notesArea.className = "notes-field";\r
+       var tagsInput = document.createElement("input");\r
+       tagsInput.name = "tags_" + id;\r
+       tagsInput.value = tags;\r
+       tagsInput.className = "tags-field";\r
+       dataNode.appendChild(stickInDiv(bodyArea));\r
+       dataNode.appendChild(stickInDiv(notesArea));\r
+       dataNode.appendChild(stickInDiv(tagsInput));\r
+       document.getElementById("a" + id + "-1").checked = true;\r
+}\r
+\r
+function stickInDiv (node) {\r
+       var div = document.createElement("div");\r
+       div.className = "field-container";\r
+       div.appendChild(node);\r
+       return div;\r
+}\r
+\r
+function getNodeText (id) {\r
+       var node = document.getElementById(id);\r
+       if (!node) return "";\r
+       var text = "";\r
+       for (var i = 0; i < node.childNodes.length; i++) {\r
+               var child = node.childNodes[i];\r
+               if (child.nodeType == 3) {\r
+                       text += child.nodeValue.replace(/ *[\r\n]/g, "");\r
+               }\r
+               else if (child.nodeName && child.nodeName.toLowerCase() == "br") {\r
+                       // \n breaks in IE\r
+                       text += "\r";\r
+               }\r
+       }\r
+       return text;\r
+}\r
+\r
+function insertEventLog () {\r
+       var node = document.getElementById("event-log-placeholder");\r
+       if (!node || !ajaxSupported()) return;\r
+       var div = document.createElement("div");\r
+       var navTop = createEventLogNavigation();\r
+       navTop.id = "event-log-navigation-top";\r
+       var navBottom = createEventLogNavigation();\r
+       navBottom.id = "event-log-navigation-bottom";\r
+       div.appendChild(navTop);\r
+       var table = document.createElement("table");\r
+       eventLogTable = table;\r
+       eventLogHeaders = new Array();\r
+       table.id = "event-log-table";\r
+       var thead = document.createElement("thead");\r
+       var thr = document.createElement("tr");\r
+       var cols = [ "id", "date", "username", "event" ];\r
+       for (var i = 0; i < cols.length; i++) {\r
+               var col = cols[i];\r
+               var th = document.createElement("th");\r
+               th.className = col;\r
+               var text = eventLogLocale[col];\r
+               var cont;\r
+               if (col == "id") {\r
+                       cont = document.createElement("a");\r
+                       var href = document.createAttribute("href");\r
+                       var asc = (eventLogURLParam["asc"] > 0);\r
+                       href.value = getEventLogURL(true, "asc", asc ? 0 : 1);\r
+                       cont.setAttributeNode(href);\r
+                       var up = String.fromCharCode(0x25B2);\r
+                       var down = String.fromCharCode(0x25BC);\r
+                       var tn = document.createTextNode(asc ? up : down);\r
+                       cont.appendChild(tn);\r
+                       cont.onclick = function () {\r
+                               var asc = !(eventLogURLParam["asc"] > 0);\r
+                               tn.nodeValue = (asc ? up : down);\r
+                               href.value = getEventLogURL(true, "asc", asc ? 1 : 0);\r
+                               eventLogURLParam["asc"] = (asc ? 1 : 0);\r
+                               updateEventLog(true);\r
+                               return false;\r
+                       };\r
+               }\r
+               else\r
+                       cont = document.createTextNode(text);\r
+               th.appendChild(cont);\r
+               thr.appendChild(th);\r
+               eventLogHeaders[col] = th;\r
+       }\r
+       thead.appendChild(thr);\r
+       table.appendChild(thead);\r
+       eventLogTableBody = document.createElement("tbody");\r
+       table.appendChild(eventLogTableBody);\r
+       div.appendChild(table);\r
+       div.appendChild(navBottom);\r
+       node.parentNode.replaceChild(div, node);\r
+       updateEventLog();\r
+}\r
+\r
+function createEventLogNavigation () {\r
+       var div = document.createElement("div");\r
+       div.className = "event-log-navigation";\r
+       var prev = document.createElement("a");\r
+       var pHref = document.createAttribute("href");\r
+       prev.setAttributeNode(pHref);\r
+       prev.appendChild(document.createTextNode(\r
+               String.fromCharCode(0x2190) + " " + eventLogLocale["previous"]));\r
+       prev.className = "back";\r
+       var next = document.createElement("a");\r
+       var nHref = document.createAttribute("href");\r
+       next.setAttributeNode(nHref);\r
+       next.appendChild(document.createTextNode(\r
+               eventLogLocale["next"] + " " + String.fromCharCode(0x2192)));\r
+       next.className = "forward";\r
+       var current = document.createElement("a");\r
+       var cHref = document.createAttribute("href");\r
+       current.setAttributeNode(cHref);\r
+       current.appendChild(document.createTextNode(\r
+               eventLogLocale["current"]));\r
+       current.className = "current";\r
+       div.appendChild(prev);\r
+       div.appendChild(next);\r
+       div.appendChild(current);\r
+       eventLogPreviousLinks.push(prev);\r
+       eventLogNextLinks.push(next);\r
+       eventLogCurrentLinks.push(current);\r
+       return div;\r
+}\r
+\r
+function updateEventLog (reset) {\r
+       clearEventLog();\r
+       var tr = document.createElement("tr");\r
+       var td = document.createElement("td");\r
+       td.colSpan = 4;\r
+       td.className = "loading";\r
+       td.appendChild(document.createTextNode(eventLogLocale["loading"]));\r
+       tr.appendChild(td);\r
+       eventLogTableBody.appendChild(tr);\r
+       eventLogHeaders["event"].className = "event" + (eventLogURLParam["code"] != null ? " filtered" : "");\r
+       eventLogHeaders["username"].className = "username" + (eventLogURLParam["user"] != null ? " filtered" : "");\r
+       if (eventLogRequest) eventLogRequest.abort();\r
+       eventLogRequest = getAjaxObject();\r
+       eventLogRequest.onreadystatechange = checkEventLogRequest;\r
+       var url = getEventLogURL(reset);\r
+       for (var i = 0; i < eventLogCurrentLinks.length; i++) {\r
+               var link = eventLogCurrentLinks[i];\r
+               link.getAttributeNode("href").value = url;\r
+       }\r
+       url += "&output=xml";\r
+       eventLogRequest.open("GET", url, true);\r
+       eventLogRequest.send("");\r
+}\r
+\r
+function checkEventLogRequest () {\r
+       if (eventLogRequest.readyState != 4 || eventLogRequest.status != 200) return;\r
+       var xml = eventLogRequest.responseXML;\r
+       eventLogRequest = null;\r
+       var data = parseEventLogXML(xml);\r
+       if (data == null) {\r
+               return;\r
+       }\r
+       fillEventLog(data);\r
+}\r
+\r
+function clearEventLog () {\r
+       setEventLogNavigationEnabled(false, false);\r
+       setEventLogNavigationEnabled(true, false);\r
+       while (eventLogTableBody.firstChild)\r
+               eventLogTableBody.removeChild(eventLogTableBody.firstChild);\r
+}\r
+\r
+function fillEventLog (tableData) {\r
+       clearEventLog();\r
+       setEventLogNavigationEnabled(false, tableData["leading"]);\r
+       setEventLogNavigationEnabled(true, tableData["trailing"]);\r
+       var events = tableData["events"];\r
+       var dataFilter;\r
+       if (eventLogURLParam["data"])\r
+               dataFilter = eventLogURLParam["data"].replace(/=.*/, "");\r
+       for (var i = 0; i < events.length; i++) {\r
+               var evt = events[i];\r
+               var data = evt["data"];\r
+               var evenOdd = (i % 2 == 0 ? "even" : "odd");\r
+               var firstRow = document.createElement("tr");\r
+               firstRow.className = evenOdd;\r
+               var idCell = document.createElement("td");\r
+               idCell.className = "id";\r
+               var rowSpan = 1;\r
+               for (name in data) rowSpan++;\r
+               idCell.rowSpan = rowSpan;\r
+               idCell.appendChild(document.createTextNode(evt["id"]));\r
+               firstRow.appendChild(idCell);\r
+               var dateCell = document.createElement("td");\r
+               dateCell.className = "date";\r
+               dateCell.appendChild(document.createTextNode(evt["date"]));\r
+               firstRow.appendChild(dateCell);\r
+               var userCell = document.createElement("td");\r
+               userCell.className = "username";\r
+               var userID = evt["userid"];\r
+               var username;\r
+               if (userID == 0) {\r
+                       userCell.className += " guest";\r
+                       username = eventLogLocale["guest"];\r
+               }\r
+               else if ("username" in evt) {\r
+                       username = evt["username"];\r
+               }\r
+               else {\r
+                       userCell.className += " removed-account";\r
+                       username = "#" + userID;\r
+               }\r
+               var userLink = createEventLogLink("user", userID);\r
+               userLink.appendChild(document.createTextNode(username));\r
+               userCell.appendChild(userLink);\r
+               firstRow.appendChild(userCell);\r
+               var descCell = document.createElement("td");\r
+               descCell.className = "event";\r
+               var descLink = document.createElement("a");\r
+               var descHref = document.createAttribute("href");\r
+               var descLink = createEventLogLink("code", evt["code"]);\r
+               descLink.appendChild(document.createTextNode(evt["description"]));\r
+               descCell.appendChild(descLink);\r
+               firstRow.appendChild(descCell);\r
+               eventLogTableBody.appendChild(firstRow);\r
+               for (name in data) {\r
+                       var value = data[name];\r
+                       var row = document.createElement("tr");\r
+                       row.className = evenOdd;\r
+                       var nameCell = document.createElement("td");\r
+                       nameCell.className = "property-name";\r
+                       if (dataFilter && name == dataFilter)\r
+                               nameCell.className += " filtered";\r
+                       nameCell.appendChild(document.createTextNode(name));\r
+                       var valueCell = document.createElement("td");\r
+                       valueCell.className = "property-value";\r
+                       valueCell.colSpan = 2;\r
+                       if (value.length > 1) {\r
+                               valueCell.appendChild(document.createTextNode(fixWhiteSpace(value[0])));\r
+                               for (var j = 1; j < value.length; j++) {\r
+                                       valueCell.appendChild(document.createElement("br"));\r
+                                       valueCell.appendChild(document.createTextNode(fixWhiteSpace(value[j])));\r
+                               }\r
+                       }\r
+                       else {\r
+                               var empty = (value.length == 0);\r
+                               var val = (!empty ? value[0].replace(/'/g, '\\\'') : "");\r
+                               var link = createEventLogLink("data", name + "=" + val);\r
+                               var text;\r
+                               if (empty) {\r
+                                       link.className = "empty";\r
+                                       text = eventLogLocale["empty"];\r
+                               }\r
+                               else\r
+                                       text = fixWhiteSpace(value[0]);\r
+                               link.appendChild(document.createTextNode(text));\r
+                               valueCell.appendChild(link);\r
+                       }\r
+                       row.appendChild(nameCell);\r
+                       row.appendChild(valueCell);\r
+                       eventLogTableBody.appendChild(row);\r
+               }\r
+       }\r
+}\r
+\r
+function setEventLogNavigationEnabled (next, enabled) {\r
+       var links, className;\r
+       if (next) {\r
+               links = eventLogNextLinks;\r
+               className = "forward";\r
+       }\r
+       else {\r
+               links = eventLogPreviousLinks;\r
+               className = "back";\r
+       }\r
+       for (var i = 0; i < links.length; i++) {\r
+               var link = links[i];\r
+               var href = link.getAttributeNode("href");\r
+               if (enabled) {\r
+                       link.className = className;\r
+                       var start = eventLogURLParam["start"];\r
+                       var count = eventLogURLParam["count"];\r
+                       if (!next)\r
+                               start = (start >= count ? start - count : 0);\r
+                       else\r
+                               start += count;\r
+                       link.onclick = function () {\r
+                               eventLogURLParam["start"] = start;\r
+                               updateEventLog();\r
+                               return false;\r
+                       };\r
+                       href.value = getEventLogURL(false, "start", start);\r
+               }\r
+               else {\r
+                       link.className = className + " inactive";\r
+                       link.onclick = function () {\r
+                               return false;\r
+                       };\r
+                       href.value = "#";\r
+               }\r
+       }\r
+}\r
+\r
+function parseEventLogXML (xml) {\r
+       if (!xml || !xml.childNodes) return null;\r
+       var leading = false;\r
+       var trailing = false;\r
+       var events = new Array();\r
+       for (var i = 0; i < xml.childNodes.length; i++) {\r
+               var root = xml.childNodes[i];\r
+               if (root.nodeType != 1) continue;\r
+               for (var j = 0; j < root.childNodes.length; j++) {\r
+                       var child = root.childNodes[j];\r
+                       if (child.nodeType == 1) {\r
+                               switch (child.nodeName) {\r
+                                       case "event":\r
+                                               events.push(parseLogEventNode(child));\r
+                                               break;\r
+                                       case "leading":\r
+                                               leading = true;\r
+                                               break;\r
+                                       case "trailing":\r
+                                               trailing = true;\r
+                                               break;\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+       var data = new Array();\r
+       data["leading"] = leading;\r
+       data["trailing"] = trailing;\r
+       data["events"] = events;\r
+       return data;\r
+}\r
+\r
+function parseLogEventNode (node) {\r
+       var event = new Array();\r
+       event["data"] = new Array();\r
+       for (var i = 0; i < node.childNodes.length; i++) {\r
+               var child = node.childNodes[i];\r
+               if (child.nodeType != 1) continue;\r
+               if (child.nodeName == "data") {\r
+                       var name, value;\r
+                       for (var j = 0; j < child.childNodes.length; j++) {\r
+                               var n = child.childNodes[j];\r
+                               if (n.nodeType != 1) continue;\r
+                               switch (n.nodeName) {\r
+                                       case "name":\r
+                                               name = n.firstChild.nodeValue;\r
+                                               break;\r
+                                       case "value":\r
+                                               value = extractLogEventDataValue(n);\r
+                                               break;\r
+                               }\r
+                       }\r
+                       event["data"][name] = value;\r
+               }\r
+               else {\r
+                       event[child.nodeName] = child.firstChild.nodeValue;\r
+               }\r
+       }\r
+       return event;\r
+}\r
+\r
+function extractLogEventDataValue (node) {\r
+       var value = new Array();\r
+       for (var i = 0; i < node.childNodes.length; i++) {\r
+               var child = node.childNodes[i];\r
+               if (child.nodeType != 3) continue;\r
+               value.push(child.nodeValue);\r
+       }\r
+       return value;\r
+}\r
+\r
+function createEventLogLink (name, value) {\r
+       var anchor = document.createElement("a");\r
+       var href = document.createAttribute("href");\r
+       href.value = getEventLogURL(true, name, value);\r
+       anchor.setAttributeNode(href);\r
+       anchor.onclick = function () {\r
+               eventLogURLParam[name]\r
+                       = (eventLogURLParam[name] == value ? null : value);\r
+               updateEventLog(true);\r
+               return false;\r
+       };\r
+       return anchor;\r
+}\r
+\r
+function getEventLogURL (reset, name, value) {\r
+       var pairs = new Array();\r
+       var found = false;\r
+       for (key in eventLogURLParam) {\r
+               var val;\r
+               switch (key) {\r
+                       case name:\r
+                               val = (eventLogURLParam[key] == value ? null : value);\r
+                               found = true;\r
+                               break;\r
+                       case "start":\r
+                               val = (reset ? 0 : eventLogURLParam[key]);\r
+                               break;\r
+                       default:\r
+                               val = eventLogURLParam[key];\r
+               }\r
+               if (val != null)\r
+                       pairs.push(escape(key) + "=" + escape(val));\r
+       }\r
+       if (name && !found) {\r
+               pairs.push(escape(name) + "=" + escape(value));\r
+       }\r
+       return eventLogURL + pairs.join("&");\r
+}\r
+\r
+function fixWhiteSpace (text) {\r
+       var nbsp = String.fromCharCode(160);\r
+       return text\r
+               .replace(/[\r\n]+/g, "")\r
+               .replace(/^\s/, nbsp)\r
+               .replace(/\s$/, nbsp)\r
+               .replace(/\s{2}/g, nbsp + nbsp);\r
+}\r
+\r
+function addOnloadFunction (f) {\r
+       if (window.onload != null) {\r
+               var old = window.onload;\r
+               window.onload = function (e) {\r
+                       old(e);\r
+                       f();\r
+               };\r
+       }\r
+       else {\r
+               window.onload = f;\r
+       }\r
+}\r
+\r
+addOnloadFunction(insertEventLog);
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/ajax.js b/pub/qdb/res/themes/default/js/ajax.js
new file mode 100644 (file)
index 0000000..d31d654
--- /dev/null
@@ -0,0 +1,53 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# ajax.js                                                                     #\r
+# Facilitates access to the browser's AJAX features, if any                   #\r
+###############################################################################\r
+# $Id:: ajax.js 291 2007-02-05 21:24:46Z ceetee                             $ #\r
+###############################################################################\r
+*/\r
+\r
+var ajaxMethods = new Array(\r
+       function() { return new ActiveXObject("Msxml2.XMLHTTP") },\r
+       function() { return new ActiveXObject("Microsoft.XMLHTTP") },\r
+       function() { return new XMLHttpRequest() }\r
+);\r
+\r
+var ajaxMethodIndex = -1;\r
+\r
+for (var i = 0; i < ajaxMethods.length; i++) {\r
+       try {\r
+               ajaxMethods[i]();\r
+               ajaxMethodIndex = i;\r
+               break;\r
+       } catch (e) { }\r
+}\r
+\r
+function ajaxSupported () {\r
+       return (ajaxMethodIndex >= 0);\r
+}\r
+\r
+function getAjaxObject () {\r
+       return (ajaxSupported()\r
+               ? ajaxMethods[ajaxMethodIndex]()\r
+               : null);\r
+}
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/excanvas/AUTHORS b/pub/qdb/res/themes/default/js/excanvas/AUTHORS
new file mode 100644 (file)
index 0000000..5813b92
--- /dev/null
@@ -0,0 +1 @@
+opensource@google.com
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/excanvas/COPYING b/pub/qdb/res/themes/default/js/excanvas/COPYING
new file mode 100644 (file)
index 0000000..75b5248
--- /dev/null
@@ -0,0 +1,202 @@
+\r
+                                 Apache License\r
+                           Version 2.0, January 2004\r
+                        http://www.apache.org/licenses/\r
+\r
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r
+\r
+   1. Definitions.\r
+\r
+      "License" shall mean the terms and conditions for use, reproduction,\r
+      and distribution as defined by Sections 1 through 9 of this document.\r
+\r
+      "Licensor" shall mean the copyright owner or entity authorized by\r
+      the copyright owner that is granting the License.\r
+\r
+      "Legal Entity" shall mean the union of the acting entity and all\r
+      other entities that control, are controlled by, or are under common\r
+      control with that entity. For the purposes of this definition,\r
+      "control" means (i) the power, direct or indirect, to cause the\r
+      direction or management of such entity, whether by contract or\r
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the\r
+      outstanding shares, or (iii) beneficial ownership of such entity.\r
+\r
+      "You" (or "Your") shall mean an individual or Legal Entity\r
+      exercising permissions granted by this License.\r
+\r
+      "Source" form shall mean the preferred form for making modifications,\r
+      including but not limited to software source code, documentation\r
+      source, and configuration files.\r
+\r
+      "Object" form shall mean any form resulting from mechanical\r
+      transformation or translation of a Source form, including but\r
+      not limited to compiled object code, generated documentation,\r
+      and conversions to other media types.\r
+\r
+      "Work" shall mean the work of authorship, whether in Source or\r
+      Object form, made available under the License, as indicated by a\r
+      copyright notice that is included in or attached to the work\r
+      (an example is provided in the Appendix below).\r
+\r
+      "Derivative Works" shall mean any work, whether in Source or Object\r
+      form, that is based on (or derived from) the Work and for which the\r
+      editorial revisions, annotations, elaborations, or other modifications\r
+      represent, as a whole, an original work of authorship. For the purposes\r
+      of this License, Derivative Works shall not include works that remain\r
+      separable from, or merely link (or bind by name) to the interfaces of,\r
+      the Work and Derivative Works thereof.\r
+\r
+      "Contribution" shall mean any work of authorship, including\r
+      the original version of the Work and any modifications or additions\r
+      to that Work or Derivative Works thereof, that is intentionally\r
+      submitted to Licensor for inclusion in the Work by the copyright owner\r
+      or by an individual or Legal Entity authorized to submit on behalf of\r
+      the copyright owner. For the purposes of this definition, "submitted"\r
+      means any form of electronic, verbal, or written communication sent\r
+      to the Licensor or its representatives, including but not limited to\r
+      communication on electronic mailing lists, source code control systems,\r
+      and issue tracking systems that are managed by, or on behalf of, the\r
+      Licensor for the purpose of discussing and improving the Work, but\r
+      excluding communication that is conspicuously marked or otherwise\r
+      designated in writing by the copyright owner as "Not a Contribution."\r
+\r
+      "Contributor" shall mean Licensor and any individual or Legal Entity\r
+      on behalf of whom a Contribution has been received by Licensor and\r
+      subsequently incorporated within the Work.\r
+\r
+   2. Grant of Copyright License. Subject to the terms and conditions of\r
+      this License, each Contributor hereby grants to You a perpetual,\r
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r
+      copyright license to reproduce, prepare Derivative Works of,\r
+      publicly display, publicly perform, sublicense, and distribute the\r
+      Work and such Derivative Works in Source or Object form.\r
+\r
+   3. Grant of Patent License. Subject to the terms and conditions of\r
+      this License, each Contributor hereby grants to You a perpetual,\r
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r
+      (except as stated in this section) patent license to make, have made,\r
+      use, offer to sell, sell, import, and otherwise transfer the Work,\r
+      where such license applies only to those patent claims licensable\r
+      by such Contributor that are necessarily infringed by their\r
+      Contribution(s) alone or by combination of their Contribution(s)\r
+      with the Work to which such Contribution(s) was submitted. If You\r
+      institute patent litigation against any entity (including a\r
+      cross-claim or counterclaim in a lawsuit) alleging that the Work\r
+      or a Contribution incorporated within the Work constitutes direct\r
+      or contributory patent infringement, then any patent licenses\r
+      granted to You under this License for that Work shall terminate\r
+      as of the date such litigation is filed.\r
+\r
+   4. Redistribution. You may reproduce and distribute copies of the\r
+      Work or Derivative Works thereof in any medium, with or without\r
+      modifications, and in Source or Object form, provided that You\r
+      meet the following conditions:\r
+\r
+      (a) You must give any other recipients of the Work or\r
+          Derivative Works a copy of this License; and\r
+\r
+      (b) You must cause any modified files to carry prominent notices\r
+          stating that You changed the files; and\r
+\r
+      (c) You must retain, in the Source form of any Derivative Works\r
+          that You distribute, all copyright, patent, trademark, and\r
+          attribution notices from the Source form of the Work,\r
+          excluding those notices that do not pertain to any part of\r
+          the Derivative Works; and\r
+\r
+      (d) If the Work includes a "NOTICE" text file as part of its\r
+          distribution, then any Derivative Works that You distribute must\r
+          include a readable copy of the attribution notices contained\r
+          within such NOTICE file, excluding those notices that do not\r
+          pertain to any part of the Derivative Works, in at least one\r
+          of the following places: within a NOTICE text file distributed\r
+          as part of the Derivative Works; within the Source form or\r
+          documentation, if provided along with the Derivative Works; or,\r
+          within a display generated by the Derivative Works, if and\r
+          wherever such third-party notices normally appear. The contents\r
+          of the NOTICE file are for informational purposes only and\r
+          do not modify the License. You may add Your own attribution\r
+          notices within Derivative Works that You distribute, alongside\r
+          or as an addendum to the NOTICE text from the Work, provided\r
+          that such additional attribution notices cannot be construed\r
+          as modifying the License.\r
+\r
+      You may add Your own copyright statement to Your modifications and\r
+      may provide additional or different license terms and conditions\r
+      for use, reproduction, or distribution of Your modifications, or\r
+      for any such Derivative Works as a whole, provided Your use,\r
+      reproduction, and distribution of the Work otherwise complies with\r
+      the conditions stated in this License.\r
+\r
+   5. Submission of Contributions. Unless You explicitly state otherwise,\r
+      any Contribution intentionally submitted for inclusion in the Work\r
+      by You to the Licensor shall be under the terms and conditions of\r
+      this License, without any additional terms or conditions.\r
+      Notwithstanding the above, nothing herein shall supersede or modify\r
+      the terms of any separate license agreement you may have executed\r
+      with Licensor regarding such Contributions.\r
+\r
+   6. Trademarks. This License does not grant permission to use the trade\r
+      names, trademarks, service marks, or product names of the Licensor,\r
+      except as required for reasonable and customary use in describing the\r
+      origin of the Work and reproducing the content of the NOTICE file.\r
+\r
+   7. Disclaimer of Warranty. Unless required by applicable law or\r
+      agreed to in writing, Licensor provides the Work (and each\r
+      Contributor provides its Contributions) on an "AS IS" BASIS,\r
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r
+      implied, including, without limitation, any warranties or conditions\r
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r
+      PARTICULAR PURPOSE. You are solely responsible for determining the\r
+      appropriateness of using or redistributing the Work and assume any\r
+      risks associated with Your exercise of permissions under this License.\r
+\r
+   8. Limitation of Liability. In no event and under no legal theory,\r
+      whether in tort (including negligence), contract, or otherwise,\r
+      unless required by applicable law (such as deliberate and grossly\r
+      negligent acts) or agreed to in writing, shall any Contributor be\r
+      liable to You for damages, including any direct, indirect, special,\r
+      incidental, or consequential damages of any character arising as a\r
+      result of this License or out of the use or inability to use the\r
+      Work (including but not limited to damages for loss of goodwill,\r
+      work stoppage, computer failure or malfunction, or any and all\r
+      other commercial damages or losses), even if such Contributor\r
+      has been advised of the possibility of such damages.\r
+\r
+   9. Accepting Warranty or Additional Liability. While redistributing\r
+      the Work or Derivative Works thereof, You may choose to offer,\r
+      and charge a fee for, acceptance of support, warranty, indemnity,\r
+      or other liability obligations and/or rights consistent with this\r
+      License. However, in accepting such obligations, You may act only\r
+      on Your own behalf and on Your sole responsibility, not on behalf\r
+      of any other Contributor, and only if You agree to indemnify,\r
+      defend, and hold each Contributor harmless for any liability\r
+      incurred by, or claims asserted against, such Contributor by reason\r
+      of your accepting any such warranty or additional liability.\r
+\r
+   END OF TERMS AND CONDITIONS\r
+\r
+   APPENDIX: How to apply the Apache License to your work.\r
+\r
+      To apply the Apache License to your work, attach the following\r
+      boilerplate notice, with the fields enclosed by brackets "[]"\r
+      replaced with your own identifying information. (Don't include\r
+      the brackets!)  The text should be enclosed in the appropriate\r
+      comment syntax for the file format. We also recommend that a\r
+      file or class name and description of purpose be included on the\r
+      same "printed page" as the copyright notice for easier\r
+      identification within third-party archives.\r
+\r
+   Copyright [yyyy] [name of copyright owner]\r
+\r
+   Licensed under the Apache License, Version 2.0 (the "License");\r
+   you may not use this file except in compliance with the License.\r
+   You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
diff --git a/pub/qdb/res/themes/default/js/excanvas/README b/pub/qdb/res/themes/default/js/excanvas/README
new file mode 100644 (file)
index 0000000..eb7c42f
--- /dev/null
@@ -0,0 +1,22 @@
+ExplorerCanvas\r
+Copyright 2006 Google Inc.\r
+\r
+-------------------------------------------------------------------------------\r
+DESCRIPTION\r
+\r
+Firefox, Safari and Opera 9 support the canvas tag to allow 2D command-based \r
+drawing operations. ExplorerCanvas brings the same functionality to Internet \r
+Explorer; web developers only need to include a single script tag in their \r
+existing canvas webpages to enable this support.\r
+\r
+\r
+-------------------------------------------------------------------------------\r
+INSTALLATION\r
+\r
+Include the ExplorerCanvas tag in the same directory as your HTML files, and \r
+add the following code to your page, preferably in the <head> tag.\r
+\r
+<!--[if IE]><script type="text/javascript" src="excanvas.js"></script><![endif]-->\r
+\r
+If you run into trouble, please look at the included example code to see how\r
+to best implement this
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/excanvas/examples/example1.html b/pub/qdb/res/themes/default/js/excanvas/examples/example1.html
new file mode 100644 (file)
index 0000000..d67ebde
--- /dev/null
@@ -0,0 +1,93 @@
+<!--\r
+       Copyright 2006 Google Inc.\r
+\r
+       Licensed under the Apache License, Version 2.0 (the "License");\r
+       you may not use this file except in compliance with the License.\r
+       You may obtain a copy of the License at\r
+\r
+         http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+       Unless required by applicable law or agreed to in writing, software\r
+       distributed under the License is distributed on an "AS IS" BASIS,\r
+       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+       See the License for the specific language governing permissions and\r
+       limitations under the License.\r
+-->\r
+<html>\r
+<head>\r
+       <title>ExplorerCanvas Example 1</title>\r
+       <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->\r
+       <script type="text/javascript">\r
+               var canvas, ctx;\r
+               var particles = [];\r
+               var NUM_PARTICLES = 20;\r
+\r
+               function Particle() {\r
+                       this.x = Math.random() * canvas.width;\r
+                       this.y = Math.random() * canvas.height;\r
+\r
+                       this.xvel = Math.random() * 5 - 2.5;\r
+                       this.yvel = Math.random() * 5 - 2.5;\r
+               }\r
+\r
+               Particle.prototype.update = function() {\r
+                       this.x += this.xvel;\r
+                       this.y += this.yvel;\r
+\r
+                       this.yvel += 0.1;\r
+\r
+                       if (this.x > canvas.width || this.x < 0) {\r
+                               this.xvel = -this.xvel;\r
+                       }\r
+\r
+                       if (this.y > canvas.height || this.y < 0) {\r
+                               this.yvel = -this.yvel;\r
+                       }\r
+               }\r
+\r
+               function loop() {\r
+                       ctx.clearRect(0, 0, canvas.width, canvas.height);\r
+\r
+                       for(var i = 0; i < NUM_PARTICLES; i++) {\r
+                               particles[i].update();\r
+\r
+                               ctx.beginPath();\r
+                               ctx.moveTo(particles[i].x, particles[i].y);\r
+                               ctx.lineTo(particles[i].x - particles[i].xvel,\r
+                                                                        particles[i].y - particles[i].yvel);\r
+                               ctx.stroke();\r
+                               ctx.closePath();\r
+                       }\r
+\r
+                       setTimeout(loop, 10);\r
+               }\r
+\r
+               function load() {\r
+                       canvas = document.getElementById("cv");\r
+                       ctx = canvas.getContext("2d");\r
+\r
+                       for(var i = 0; i < NUM_PARTICLES; i++) {\r
+                               particles[i] = new Particle();\r
+                       }\r
+\r
+                       ctx.lineWidth = "2";\r
+                       ctx.strokeStyle = "rgb(255, 255, 255)";\r
+                       loop();\r
+               }\r
+       </script>\r
+       <style>\r
+               body {\r
+                       background-color:black;\r
+                       margin:50px;\r
+                       text-align:center;\r
+               }\r
+\r
+               canvas {\r
+                       border:1px solid #444;\r
+               }\r
+       </style>\r
+</head>\r
+<body onload="load();">\r
+       <canvas id="cv" width="400" height="300"></canvas>\r
+</body>\r
+</html>
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/excanvas/examples/example2.html b/pub/qdb/res/themes/default/js/excanvas/examples/example2.html
new file mode 100644 (file)
index 0000000..55f163b
--- /dev/null
@@ -0,0 +1,513 @@
+<!--\r
+       Copyright 2006 Google Inc.\r
+\r
+       Licensed under the Apache License, Version 2.0 (the "License");\r
+       you may not use this file except in compliance with the License.\r
+       You may obtain a copy of the License at\r
+\r
+         http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+       Unless required by applicable law or agreed to in writing, software\r
+       distributed under the License is distributed on an "AS IS" BASIS,\r
+       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+       See the License for the specific language governing permissions and\r
+       limitations under the License.\r
+-->\r
+<html>\r
+<head>\r
+       <title>ExplorerCanvas Example 1</title>\r
+       <!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->\r
+       <script type="text/javascript">\r
+               /* -------------------------------------------------------------------- */\r
+\r
+               var canvas, ctx;\r
+               var canvasWidth, halfCanvasWidth;\r
+               var canvasHeight, halfCanvasHeight;\r
+\r
+               var space;  // 3D Engine\r
+               var scene;  // 3D Scene\r
+\r
+               /* -------------------------------------------------------------------- */\r
+\r
+               /**\r
+                * Space is a simple 3D system.\r
+                *\r
+                * Y+ = up\r
+                * Z+ = into screen\r
+                * X+ = right\r
+                */\r
+               function Space() {\r
+                       this.m = this.createMatrixIdentity();\r
+                       this.mStack = [];\r
+               }\r
+\r
+               Space.prototype.createMatrixIdentity = function() {\r
+                       return [\r
+                               [1, 0, 0, 0],\r
+                               [0, 1, 0, 0],\r
+                               [0, 0, 1, 0],\r
+                               [0, 0, 0, 1]\r
+                       ];\r
+               }\r
+\r
+               /**\r
+                * Multiplies two 4x4 matricies together.\r
+                */\r
+               Space.prototype.matrixMultiply = function(m1, m2) {\r
+                       var result = this.createMatrixIdentity();\r
+\r
+                       var width = m1[0].length;\r
+                       var height = m1.length;\r
+\r
+                       if (width != m2.length) {\r
+                               // error\r
+                       }\r
+\r
+                       for (var x = 0; x < width; x++) {\r
+                               for (var y = 0; y < height; y++) {\r
+                                       var sum = 0;\r
+\r
+                                       for (var z = 0; z < width; z++) {\r
+                                               sum += m1[y][z] * m2[z][x];\r
+                                       }\r
+\r
+                                       result[y][x] = sum;\r
+                               }\r
+                       }\r
+\r
+                       return result;\r
+               }\r
+\r
+               /**\r
+                * Transforms a coordinate using the current transformation\r
+                * matrix, then flattens it using the projection matrix.\r
+                */\r
+               Space.prototype.flatten = function(point) {\r
+                       var p = [[point.x, point.y, point.z, 1]];\r
+                       var pm = this.matrixMultiply(p, this.m);\r
+\r
+                       point.tx = pm[0][0];\r
+                       point.ty = pm[0][1];\r
+                       point.tz = pm[0][2];\r
+\r
+                       // lazy projection\r
+                       point.fx = halfCanvasWidth + (canvasWidth * point.tx / point.tz);\r
+                       point.fy = halfCanvasHeight -(canvasWidth * point.ty / point.tz);\r
+               }\r
+\r
+               /**\r
+                * Translate (move) the current transformation matrix\r
+                */\r
+               Space.prototype.translate = function(x, y, z) {\r
+                       var m = [\r
+                               [1, 0, 0, 0],\r
+                               [0, 1, 0, 0],\r
+                               [0, 0, 1, 0],\r
+                               [x, y, z, 1]\r
+                       ];\r
+\r
+                       this.m = this.matrixMultiply(m, this.m);\r
+               }\r
+\r
+               /**\r
+                * Rotate the current transformation matrix. Rotations are\r
+                * world-oriented, and occur in y,x,z order.\r
+                */\r
+               Space.prototype.rotate = function(x, y, z) {\r
+                       if (y) {\r
+                               var cosY = Math.cos(y);\r
+                               var sinY = Math.sin(y);\r
+                               var rotY = [\r
+                                       [cosY, 0, sinY, 0],\r
+                                       [0, 1, 0, 0],\r
+                                       [-sinY, 0, cosY, 0],\r
+                                       [0, 0, 0, 1]\r
+                               ];\r
+\r
+                               this.m = this.matrixMultiply(this.m, rotY);\r
+                       }\r
+\r
+                       if (x) {\r
+                               var cosX = Math.cos(x);\r
+                               var sinX = Math.sin(x);\r
+                               var rotX = [\r
+                                       [1, 0, 0, 0],\r
+                                       [0, cosX, -sinX, 0],\r
+                                       [0, sinX, cosX,0],\r
+                                       [0, 0, 0, 1]\r
+                               ];\r
+                               this.m = this.matrixMultiply(this.m, rotX);\r
+                       }\r
+\r
+                       if (z) {\r
+                               var cosZ = Math.cos(z);\r
+                               var sinZ = Math.sin(z);\r
+                               var rotZ = [\r
+                                       [cosZ, -sinZ, 0, 0],\r
+                                       [sinZ, cosZ, 0, 0],\r
+                                       [0, 0, 1, 0],\r
+                                       [0, 0, 0, 1]\r
+                               ];\r
+\r
+                               this.m = this.matrixMultiply(this.m, rotZ);\r
+                       }\r
+               }\r
+\r
+               /**\r
+                * Pushes the current transformation onto the stack\r
+                */\r
+               Space.prototype.push = function() {\r
+                       this.mStack.push(this.m);\r
+                       this.m = [\r
+                               [this.m[0][0], this.m[0][1], this.m[0][2], this.m[0][3]],\r
+                               [this.m[1][0], this.m[1][1], this.m[1][2], this.m[1][3]],\r
+                               [this.m[2][0], this.m[2][1], this.m[2][2], this.m[2][3]],\r
+                               [this.m[3][0], this.m[3][1], this.m[3][2], this.m[3][3]]\r
+                       ];\r
+               }\r
+\r
+               /**\r
+                * Pops the end off the transformation stack\r
+                */\r
+               Space.prototype.pop = function() {\r
+                       this.m = this.mStack.pop();\r
+               }\r
+\r
+               /* -------------------------------------------------------------------- */\r
+\r
+               /**\r
+                * A 3d coordinate\r
+                */\r
+               function Point(x, y, z) {\r
+                       this.x = x;\r
+                       this.y = y;\r
+                       this.z = z;\r
+\r
+                       // Relative to camera coordinates\r
+                       this.tx;\r
+                       this.ty;\r
+                       this.tz;\r
+\r
+                       // Flattened coordinates\r
+                       this.fx;\r
+                       this.fy;\r
+               }\r
+\r
+               /**\r
+                * A Shape is made up of polygons\r
+                */\r
+               function Shape() {\r
+                       this.points = [];\r
+                       this.polygons = [];\r
+               }\r
+\r
+               /**\r
+                * Draws the shape\r
+                */\r
+               Shape.prototype.draw = function(drawlist) {\r
+                       for (var i = 0; i< this.points.length; i++) {\r
+                               space.flatten(this.points[i]);\r
+                       }\r
+\r
+                       for (var i = 0; i< this.polygons.length; i++) {\r
+                               var poly = this.polygons[i]; // convenience\r
+\r
+                               space.flatten(poly.origin);\r
+\r
+                               // lazy backface culling\r
+                               if (poly.normal && this.backface) {\r
+                                       space.flatten(poly.normal);\r
+\r
+                                       var originDist = Math.pow(poly.origin.tx, 2)\r
+                                                                                                + Math.pow(poly.origin.ty, 2)\r
+                                                                                                + Math.pow(poly.origin.tz, 2);\r
+\r
+                                       var normalDist = Math.pow(poly.normal.tx, 2)\r
+                                                                                                + Math.pow(poly.normal.ty, 2)\r
+                                                                                                + Math.pow(poly.normal.tz, 2);\r
+\r
+                                       if(originDist > normalDist) {\r
+                                               drawlist.push(poly);\r
+                                       }\r
+                               } else {\r
+                                       drawlist.push(poly);\r
+                               }\r
+                       }\r
+               }\r
+\r
+               /**\r
+                * A polygon is a connection of points in the shape object. You\r
+                * should probably try to make them coplanar.\r
+                */\r
+               function Polygon(points, normal, backface, type, color) {\r
+                       this.points = points;\r
+\r
+                       this.origin = new Point(0, 0, 0);\r
+                       for(var i = 0; i < this.points.length; i++) {\r
+                               this.origin.x += this.points[i].x;\r
+                               this.origin.y += this.points[i].y;\r
+                               this.origin.z += this.points[i].z;\r
+                       }\r
+\r
+                       this.origin.x /= this.points.length;\r
+                       this.origin.y /= this.points.length;\r
+                       this.origin.z /= this.points.length;\r
+\r
+                       if (normal) {\r
+                               this.normal = new Point(this.origin.x + normal.x,\r
+                                                                                                                               this.origin.y + normal.y,\r
+                                                                                                                               this.origin.z + normal.z);\r
+                       } else {\r
+                               this.normal = null;\r
+                       }\r
+\r
+                       this.backface = backface;\r
+                       this.type = type;\r
+                       this.color = color;\r
+               }\r
+\r
+               Polygon.SOLID = 0;\r
+               Polygon.WIRE = 1;\r
+\r
+               /**\r
+                * Draws the polygon. Assumes that the points have already been\r
+                * flattened.\r
+                */\r
+               Polygon.prototype.draw = function() {\r
+                       ctx.beginPath();\r
+                       ctx.moveTo(this.points[0].fx, this.points[0].fy);\r
+\r
+                       for(var i = 0; i < this.points.length; i++) {\r
+                               ctx.lineTo(this.points[i].fx, this.points[i].fy);\r
+                       }\r
+\r
+                       ctx.closePath();\r
+\r
+                       var color = this.color;\r
+\r
+                       /*\r
+                       // Do lighting here\r
+                       lightvector = Math.abs(this.normal.x + this.normal.y);\r
+                       if(lightvector > 1) {\r
+                               lightvector = 1;\r
+                       }\r
+\r
+                       color[0] = (color[0] * lightvector).toString();\r
+                       color[1] = (color[1] * lightvector).toString();\r
+                       color[2] = (color[2] * lightvector).toString();\r
+                       */\r
+\r
+                       if (color.length > 3) {\r
+                               var style = ["rgba(",\r
+                                            color[0], ",",\r
+                                            color[1], ",",\r
+                                            color[2], ",",\r
+                                            color[3], ")"].join("");\r
+                       } else {\r
+                               var style = ["rgb(",\r
+                                            color[0], ",",\r
+                                            color[1], ",",\r
+                                            color[2], ")"].join("");\r
+                       }\r
+\r
+                       if (this.type == Polygon.SOLID) {\r
+                               ctx.fillStyle = style;\r
+                               ctx.fill();\r
+                       } else if (this.type == Polygon.WIRE) {\r
+                               ctx.strokeStyle = style;\r
+                               ctx.stroke();\r
+                       }\r
+               }\r
+\r
+               /* -------------------------------------------------------------------- */\r
+\r
+               /**\r
+                * Scene describes the 3D environment\r
+                */\r
+               function Scene() {\r
+                       this.shapes = {};\r
+                       this.camera = new Point(0, 0, 0);\r
+                       this.cameraTarget = new Point(0, 0, 0);\r
+                       this.cameraRotation = 0;\r
+\r
+                       this.drawlist = [];\r
+               }\r
+\r
+               /**\r
+                * Draw the world\r
+                */\r
+               Scene.prototype.draw = function() {\r
+                       space.push();\r
+\r
+                       // Camera transformation\r
+                       space.translate(\r
+                               -this.camera.x,\r
+                               -this.camera.y,\r
+                               -this.camera.z\r
+                       );\r
+\r
+                       // Camera rotation\r
+                       var xdiff = this.cameraTarget.x - this.camera.x;\r
+                       var ydiff = this.cameraTarget.y - this.camera.y;\r
+                       var zdiff = this.cameraTarget.z - this.camera.z;\r
+\r
+                       var xzdist = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(zdiff, 2));\r
+\r
+                       var xrot = -Math.atan2(ydiff, xzdist); // up/down rotation\r
+                       var yrot =  Math.atan2(xdiff, zdiff);  // left/right rotation\r
+\r
+                       space.rotate(xrot, yrot, this.cameraRotation);\r
+\r
+                       // Drawing\r
+                       this.drawlist = [];\r
+\r
+                       for(var i in this.shapes) {\r
+                               this.shapes[i].draw(this.drawlist);\r
+                       }\r
+\r
+                       // Depth sorting (warning: this is only enough to drive this demo - feel\r
+                       // free to contribute a better system).\r
+                       this.drawlist.sort(function (poly1, poly2) {\r
+                               return poly2.origin.tz - poly1.origin.tz;\r
+                       });\r
+\r
+                       for (var i = 0; i < this.drawlist.length; i++) {\r
+                               this.drawlist[i].draw();\r
+                       }\r
+\r
+                       space.pop();\r
+               }\r
+\r
+               /* -------------------------------------------------------------------- */\r
+\r
+               var count = 0;\r
+\r
+               function loop() {\r
+                       ctx.clearRect(0, 0, canvasWidth, canvasHeight);\r
+\r
+                       scene.camera.x = 70*Math.sin(count);\r
+                       scene.camera.y = 70;\r
+                       scene.camera.z = 70*Math.cos(count);\r
+                       scene.cameraRotation = count / 10;\r
+\r
+                       count += 0.01;\r
+                       scene.draw();\r
+               }\r
+\r
+               function load() {\r
+                       // Init drawing system\r
+                       canvas = document.getElementById("cv");\r
+                       ctx = canvas.getContext("2d");\r
+\r
+                       canvasWidth = canvas.width;\r
+                       canvasHeight = canvas.height;\r
+                       halfCanvasWidth = canvasWidth * 0.5;\r
+                       halfCanvasHeight = canvasHeight * 0.5;\r
+\r
+                       // Init 3D components\r
+                       space = new Space();\r
+                       scene = new Scene();\r
+\r
+                       // Create a box shape and add it to the scene\r
+                       scene.shapes['box'] = new Shape();\r
+                       var p = scene.shapes['box'].points; // for convenience\r
+\r
+                       p[0] = new Point(-10, -10, -10); // left  bottom front\r
+                       p[1] = new Point(10, -10, -10);  // right bottom front\r
+                       p[2] = new Point(10, 10, -10);   // right top    front\r
+                       p[3] = new Point(-10, 10, -10);  // left  top    front\r
+\r
+                       p[4] = new Point(-10, -10, 10);  // left  bottom back\r
+                       p[5] = new Point(10, -10, 10);   // right bottom back\r
+                       p[6] = new Point(10, 10, 10);    // right top    back\r
+                       p[7] = new Point(-10, 10, 10);   // left  top    back\r
+\r
+                       // Back\r
+                       scene.shapes['box'].polygons.push(new Polygon(\r
+                               [ p[0], p[1], p[2], p[3] ],\r
+                               new Point(0, 0, -1),\r
+                               true /* double-sided */,\r
+                               Polygon.SOLID,\r
+                               [255, 0, 0]\r
+                       ));\r
+\r
+                       // Front\r
+                       scene.shapes['box'].polygons.push(new Polygon(\r
+                               [ p[4], p[5], p[6], p[7] ],\r
+                               new Point(0, 0, 1),\r
+                               true /* double-sided */,\r
+                               Polygon.SOLID,\r
+                               [0, 0, 255]\r
+                       ));\r
+\r
+                       // Top\r
+                       scene.shapes['box'].polygons.push(new Polygon(\r
+                               [ p[2], p[3], p[7], p[6] ],\r
+                               new Point(0, 1, 0),\r
+                               false /* single-sided */,\r
+                               Polygon.WIRE,\r
+                               [0, 255, 0]\r
+                       ));\r
+\r
+                       // Transparent Top\r
+                       scene.shapes['box'].polygons.push(new Polygon(\r
+                               [ p[2], p[3], p[7], p[6] ],\r
+                               new Point(0, 1, 0),\r
+                               false /* single-sided */,\r
+                               Polygon.SOLID,\r
+                               [0, 255, 0, 0.4]\r
+                       ));\r
+\r
+                       // Left\r
+                       scene.shapes['box'].polygons.push(new Polygon(\r
+                               [ p[0], p[4], p[7], p[3] ],\r
+                               new Point(-1, 0, 0),\r
+                               true /* double-sided */,\r
+                               Polygon.SOLID,\r
+                               [255, 255, 0]\r
+                       ));\r
+\r
+                       // Right\r
+                       scene.shapes['box'].polygons.push(new Polygon(\r
+                               [ p[1], p[5], p[6], p[2] ],\r
+                               new Point(1, 0, 0),\r
+                               true /* double-sided */,\r
+                               Polygon.SOLID,\r
+                               [0, 255, 255]\r
+                       ));\r
+\r
+                       // Create a floor shape and add it to the scene\r
+                       scene.shapes['floor'] = new Shape();\r
+                       var p = scene.shapes['floor'].points; // for convenience\r
+\r
+                       p[0]  = new Point(-40, -10, -40);\r
+                       p[1]  = new Point(-40, -10,  40);\r
+                       p[2] = new Point( 40, -10,  40);\r
+                       p[3] = new Point( 40, -10, -40);\r
+\r
+                       // Floor\r
+                       scene.shapes['floor'].polygons.push(new Polygon(\r
+                               [ p[0], p[1], p[2], p[3] ],\r
+                               new Point(0, 1, 0),\r
+                               false /* single-sided */,\r
+                               Polygon.SOLID,\r
+                               [45, 45, 45]\r
+                       ));\r
+\r
+                       setInterval('loop()', 20);\r
+               }\r
+\r
+               /* -------------------------------------------------------------------- */\r
+       </script>\r
+       <style>\r
+       body {\r
+               background-color:black;\r
+               margin:50px;\r
+               text-align:center;\r
+       }\r
+       </style>\r
+</head>\r
+<body onload="load();">\r
+  <canvas id="cv" width="400" height="300"></canvas>\r
+</body>\r
+</html>
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/excanvas/examples/example3.html b/pub/qdb/res/themes/default/js/excanvas/examples/example3.html
new file mode 100644 (file)
index 0000000..ab95544
--- /dev/null
@@ -0,0 +1,284 @@
+<!DOCTYPE html>\r
+\r
+<!--\r
+\r
+Copyright 2006 Google Inc.\r
+\r
+Licensed under the Apache License, Version 2.0 (the "License");\r
+you may not use this file except in compliance with the License.\r
+You may obtain a copy of the License at\r
+\r
+  http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+Unless required by applicable law or agreed to in writing, software\r
+distributed under the License is distributed on an "AS IS" BASIS,\r
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+See the License for the specific language governing permissions and\r
+limitations under the License.\r
+\r
+-->\r
+\r
+<html>\r
+<head>\r
+<title></title>\r
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r
+<!--[if IE]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->\r
+<style type="text/css">\r
+\r
+body {\r
+  overflow: hidden;\r
+  width: 100%;\r
+  height: 100%;\r
+  margin: 0;\r
+}\r
+\r
+#image-rotator {\r
+  position: absolute;\r
+  left: 0;\r
+  top: 0;\r
+  width: 100%;\r
+  height: 100%;\r
+}\r
+\r
+#image-rotator .tool-bar {\r
+  text-align: center;\r
+}\r
+\r
+.tool-bar button {\r
+  margin: 0.5em 0.5em 0 0;\r
+}\r
+\r
+#image-rotator img,\r
+#image-rotator canvas {\r
+  position: absolute;\r
+}\r
+\r
+</style>\r
+<script type="text/javascript">\r
+\r
+function sawFunc(a) {\r
+  var PI = Math.PI;\r
+  var PI2 = PI / 2;\r
+  // make sure a is within 0 to PI\r
+  a = a % PI;\r
+  if (a < 0) {\r
+    a += PI;\r
+  }\r
+  if (a < PI2) {\r
+    return a / PI2;\r
+  } else {\r
+    return (PI - a) / PI2;\r
+  }\r
+}\r
+\r
+function easeInEaseOut(t) {\r
+  var t2 = t * t;\r
+  return 3 * t2 - 2 * t * t2;\r
+}\r
+\r
+function ImageRotator(el, src, w, h) {\r
+  this.element = el;\r
+  this.toolBar = el.getElementsByTagName("div")[0];\r
+  this.canvas = el.getElementsByTagName("canvas")[0];\r
+  var images = el.getElementsByTagName("img");\r
+  this.image = images[images.length - 1];\r
+  var btns = el.getElementsByTagName("button");\r
+  this.btnCw = btns[0];\r
+  this.btnCcw = btns[1];\r
+  var self = this;\r
+  this.btnCcw.onclick = function () {\r
+    self.rotateCcw();\r
+  };\r
+  this.btnCw.onclick = function () {\r
+    self.rotateCw();\r
+  };\r
+  this.image.onload = function (e) {\r
+    self.onImageLoad(e);\r
+  };\r
+  this.image.onerror = function (e) {\r
+    self.onImageError(e);\r
+  };\r
+  this.image.onabort = function (e) {\r
+    self.onImageAbort(e);\r
+  };\r
+  this.setImage(src, w, h);\r
+  this.layout();\r
+\r
+  var onResize = function () {\r
+    self.layout();\r
+  };\r
+  var onLoad = function () {\r
+    self.onWindowLoad();\r
+  };\r
+  if (window.addEventListener) {\r
+    window.addEventListener("resize", onResize, false);\r
+    window.addEventListener("load", onLoad, false);\r
+  } else if (window.attachEvent) {\r
+    window.attachEvent("onresize", onResize);\r
+    window.attachEvent("onload", onLoad);\r
+  }\r
+}\r
+\r
+ImageRotator.prototype = {\r
+  getLoaded: function () {\r
+    return this.imageLoaded && this.windowLoaded;\r
+  },\r
+  setImage: function (src, w, h) {\r
+    this.imageLoaded = false;\r
+    this.image.src = src;\r
+    this.imageWidth = w;\r
+    this.imageHeight = h;\r
+  },\r
+\r
+  layout: function () {\r
+    var PI2 = Math.PI / 2;\r
+    var h = this.element.clientHeight;\r
+    var w = this.element.clientWidth;\r
+    var th = this.toolBar.offsetHeight;\r
+    h -= this.toolBar.offsetHeight;\r
+    if (!this.ctx || !this.getLoaded()) {\r
+      this.btnCw.disabled = true;\r
+      this.btnCcw.disabled = true;\r
+      this.canvas.style.display = "none";\r
+      this.image.style.display = "block";\r
+      var ratio = Math.min(w / this.imageWidth, h / this.imageHeight, 1);\r
+      var imgW = this.imageWidth * ratio;\r
+      var imgH = this.imageHeight * ratio;\r
+      var y = th + (h - imgH) / 2;\r
+      var x = (w - imgW) / 2;\r
+      this.image.style.left = Math.round(x) + "px";\r
+      this.image.style.top = Math.round(y) + "px";\r
+      this.image.style.width = Math.round(imgW) + "px";\r
+      this.image.style.height = Math.round(imgH) + "px";\r
+    } else {\r
+      this.btnCw.disabled = this.isAnimating_;\r
+      this.btnCcw.disabled = this.isAnimating_;\r
+      this.canvas.style.display = "block";\r
+      this.image.style.display = "none";\r
+\r
+      this.canvas.style.left = 0 + "px";\r
+      this.canvas.style.top = th + "px";\r
+      this.canvas.style.width = w + "px";\r
+      this.canvas.width = w;\r
+      this.canvas.style.height = h + "px";\r
+      this.canvas.height = h;\r
+\r
+      this.ctx.save();\r
+      this.ctx.clearRect(0, 0, w, h);\r
+      this.ctx.translate(w / 2, h / 2);\r
+      this.ctx.rotate(this.rotation);\r
+      // 0 -> 1, sin(0) = 0\r
+      // PI / 2 -> H / W, sin(PI/2) = 1\r
+\r
+      // sin(PI/2) = 1 -> limit factor is w and imgH\r
+\r
+      var iw = this.imageWidth;\r
+      var ih = this.imageHeight;\r
+      var scale;\r
+      if (iw <= w && iw <= h && ih <= h && ih <= w) {\r
+        scale = 1;\r
+      } else {\r
+        var sinr = sawFunc(this.rotation);\r
+        var cosr = sawFunc(this.rotation + PI2);\r
+        var ratio1 = sinr * Math.min(w / ih, h / iw);\r
+        var ratio2 = cosr * Math.min(w / iw, h / ih);\r
+        var ratio = Math.min(1, ratio1 + ratio2);\r
+        scale = ratio;\r
+      }\r
+      this.ctx.scale(scale, scale);\r
+      this.ctx.translate(-iw / 2, -ih / 2);\r
+      this.ctx.drawImage(this.image, 0, 0, iw, ih);\r
+      this.ctx.restore();\r
+    }\r
+  },\r
+\r
+  rotation: 0,\r
+  animationDuration: 500,\r
+\r
+  rotateCcw: function () {\r
+    if (!this.isAnimating_) {\r
+      this.startTime_ = (new Date).valueOf();\r
+      this.currentAngle_ = this.rotation;\r
+      this.deltaAngle_ = Math.PI / 2;\r
+      this.isAnimating_ = true;\r
+      this.animCounter_ = 0;\r
+      this.rotate_();\r
+    }\r
+  },\r
+\r
+  rotateCw: function () {\r
+    if (!this.isAnimating_) {\r
+      this.startTime_ = (new Date).valueOf();\r
+      this.currentAngle_ = this.rotation;\r
+      this.deltaAngle_ = -Math.PI / 2;\r
+      this.isAnimating_ = true;\r
+      this.animCounter_ = 0;\r
+      this.rotate_();\r
+    }\r
+  },\r
+\r
+  rotate_: function () {\r
+    if (this.isAnimating_) {\r
+      var t = easeInEaseOut(Math.min(1, (new Date - this.startTime_) /\r
+                            this.animationDuration));\r
+      this.rotation = t * this.deltaAngle_ + this.currentAngle_;\r
+      if (t < 1) {\r
+        var self = this;\r
+        window.setTimeout(function () {\r
+          self.rotate_();\r
+        }, 10);\r
+      } else {\r
+        this.isAnimating_ = false;\r
+      }\r
+      this.layout();\r
+    }\r
+  },\r
+\r
+  onImageLoad: function (e) {\r
+    this.imageLoaded = true;\r
+    this.initCanvas();\r
+  },\r
+  onImageError: function (e) {\r
+    this.imageLoaded = false;\r
+  },\r
+  onImageAbort: function (e) {\r
+    this.imageLoaded = false;\r
+  },\r
+  onWindowLoad: function (e) {\r
+    this.windowLoaded = true;\r
+    this.initCanvas();\r
+  },\r
+\r
+  initCanvas: function () {\r
+    if (!this.ctx && this.getLoaded()) {\r
+      // IE recreates the element?\r
+      this.canvas = this.element.getElementsByTagName("canvas")[0];\r
+      this.ctx = this.canvas.getContext("2d");\r
+\r
+      if (!this.ctx) {\r
+        return;\r
+      }\r
+      this.layout();\r
+    }\r
+  }\r
+};\r
+\r
+</script>\r
+</head>\r
+<body>\r
+\r
+<div id="image-rotator">\r
+  <div class="tool-bar">\r
+    <button>Rotate Left</button><button>Rotate Right</button>\r
+  </div>\r
+  <canvas id="c"></canvas>\r
+  <img src="" alt="">\r
+</div>\r
+<script type="text/javascript">\r
+  new ImageRotator(document.getElementById("image-rotator"),\r
+                   "ff.jpg", 608, 380);\r
+</script>\r
+\r
+</body>\r
+</html>\r
+\r
diff --git a/pub/qdb/res/themes/default/js/excanvas/examples/ff.jpg b/pub/qdb/res/themes/default/js/excanvas/examples/ff.jpg
new file mode 100644 (file)
index 0000000..dc32d11
Binary files /dev/null and b/pub/qdb/res/themes/default/js/excanvas/examples/ff.jpg differ
diff --git a/pub/qdb/res/themes/default/js/excanvas/excanvas.js b/pub/qdb/res/themes/default/js/excanvas/excanvas.js
new file mode 100644 (file)
index 0000000..50542e1
--- /dev/null
@@ -0,0 +1,730 @@
+// Copyright 2006 Google Inc.\r
+//\r
+// Licensed under the Apache License, Version 2.0 (the "License");\r
+// you may not use this file except in compliance with the License.\r
+// You may obtain a copy of the License at\r
+//\r
+//   http://www.apache.org/licenses/LICENSE-2.0\r
+//\r
+// Unless required by applicable law or agreed to in writing, software\r
+// distributed under the License is distributed on an "AS IS" BASIS,\r
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+// See the License for the specific language governing permissions and\r
+// limitations under the License.\r
+\r
+// TODO: Patterns\r
+// TODO: Radial gradient\r
+// TODO: Clipping paths\r
+// TODO: Coordsize (still need to support stretching)\r
+// TODO: Painting mode\r
+// TODO: Optimize\r
+// TODO: canvas width/height sets content size in moz, border size in ie\r
+\r
+// only add this code if we do not already have a canvas implementation\r
+if (!window.CanvasRenderingContext2D) {\r
+\r
+(function () {\r
+\r
+  // alias some functions to make (compiled) code shorter\r
+  var m = Math;\r
+  var mr = m.round;\r
+  var ms = m.sin;\r
+  var mc = m.cos;\r
+\r
+  var G_vmlCanvasManager_ = {\r
+    init: function (opt_doc) {\r
+      var doc = opt_doc || document;\r
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {\r
+        var self = this;\r
+        doc.attachEvent("onreadystatechange", function () {\r
+          self.init_(doc);\r
+        });\r
+      }\r
+    },\r
+\r
+    init_: function (doc, e) {\r
+      if (doc.readyState == "complete") {\r
+        // create xmlns\r
+        if (!doc.namespaces["g_vml_"]) {\r
+          doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");\r
+        }\r
+\r
+        // setup default css\r
+        var ss = doc.createStyleSheet();\r
+        ss.cssText = "canvas{display:inline-block;overflow:hidden;" +\r
+            "text-align:left;}" +\r
+            "canvas *{behavior:url(#default#VML)}";\r
+\r
+        // find all canvas elements\r
+        var els = doc.getElementsByTagName("canvas");\r
+        for (var i = 0; i < els.length; i++) {\r
+          if (!els[i].getContext) {\r
+            this.initElement(els[i]);\r
+          }\r
+        }\r
+      }\r
+    },\r
+\r
+    fixElement_: function (el) {\r
+      // in IE before version 5.5 we would need to add HTML: to the tag name\r
+      // but we do not care about IE before version 6\r
+      var outerHTML = el.outerHTML;\r
+      var newEl = document.createElement(outerHTML);\r
+      // if the tag is still open IE has created the children as siblings and\r
+      // it has also created a tag with the name "/FOO"\r
+      if (outerHTML.slice(-2) != "/>") {\r
+        var tagName = "/" + el.tagName;\r
+        var ns;\r
+        // remove content\r
+        while ((ns = el.nextSibling) && ns.tagName != tagName) {\r
+          ns.removeNode();\r
+        }\r
+        // remove the incorrect closing tag\r
+        if (ns) {\r
+          ns.removeNode();\r
+        }\r
+      }\r
+      el.parentNode.replaceChild(newEl, el);\r
+      return newEl;\r
+    },\r
+\r
+    /**\r
+     * Public initializes a canvas element so that it can be used as canvas\r
+     * element from now on. This is called automatically before the page is\r
+     * loaded but if you are creating elements using createElement you need to\r
+     * make sure this is called on the element.\r
+     * @param {HTMLElement} el The canvas element to initialize.\r
+     * @return {HTMLElement} the element that was created.\r
+     */\r
+    initElement: function (el) {\r
+      el = this.fixElement_(el);\r
+      el.getContext = function () {\r
+        if (this.context_) {\r
+          return this.context_;\r
+        }\r
+        return this.context_ = new CanvasRenderingContext2D_(this);\r
+      };\r
+\r
+      // do not use inline function because that will leak memory\r
+      // el.attachEvent('onpropertychange', onPropertyChange)\r
+      el.attachEvent('onresize', onResize);\r
+\r
+      var attrs = el.attributes;\r
+      if (attrs.width && attrs.width.specified) {\r
+        // TODO: use runtimeStyle and coordsize\r
+        // el.getContext().setWidth_(attrs.width.nodeValue);\r
+        el.style.width = attrs.width.nodeValue + "px";\r
+      }\r
+      if (attrs.height && attrs.height.specified) {\r
+        // TODO: use runtimeStyle and coordsize\r
+        // el.getContext().setHeight_(attrs.height.nodeValue);\r
+        el.style.height = attrs.height.nodeValue + "px";\r
+      }\r
+      //el.getContext().setCoordsize_()\r
+      return el;\r
+    }\r
+  };\r
+\r
+  function onPropertyChange(e) {\r
+    // we need to watch changes to width and height\r
+    switch (e.propertyName) {\r
+      case 'width':\r
+      case 'height':\r
+        // TODO: coordsize and size\r
+        break;\r
+    }\r
+  }\r
+\r
+  function onResize(e) {\r
+    var el = e.srcElement;\r
+    if (el.firstChild) {\r
+      el.firstChild.style.width =  el.clientWidth + 'px';\r
+      el.firstChild.style.height = el.clientHeight + 'px';\r
+    }\r
+  }\r
+\r
+  G_vmlCanvasManager_.init();\r
+\r
+  // precompute "00" to "FF"\r
+  var dec2hex = [];\r
+  for (var i = 0; i < 16; i++) {\r
+    for (var j = 0; j < 16; j++) {\r
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);\r
+    }\r
+  }\r
+\r
+  function createMatrixIdentity() {\r
+    return [\r
+      [1, 0, 0],\r
+      [0, 1, 0],\r
+      [0, 0, 1]\r
+    ];\r
+  }\r
+\r
+  function matrixMultiply(m1, m2) {\r
+    var result = createMatrixIdentity();\r
+\r
+    for (var x = 0; x < 3; x++) {\r
+      for (var y = 0; y < 3; y++) {\r
+        var sum = 0;\r
+\r
+        for (var z = 0; z < 3; z++) {\r
+          sum += m1[x][z] * m2[z][y];\r
+        }\r
+\r
+        result[x][y] = sum;\r
+      }\r
+    }\r
+    return result;\r
+  }\r
+\r
+  function copyState(o1, o2) {\r
+    o2.fillStyle     = o1.fillStyle;\r
+    o2.lineCap       = o1.lineCap;\r
+    o2.lineJoin      = o1.lineJoin;\r
+    o2.lineWidth     = o1.lineWidth;\r
+    o2.miterLimit    = o1.miterLimit;\r
+    o2.shadowBlur    = o1.shadowBlur;\r
+    o2.shadowColor   = o1.shadowColor;\r
+    o2.shadowOffsetX = o1.shadowOffsetX;\r
+    o2.shadowOffsetY = o1.shadowOffsetY;\r
+    o2.strokeStyle   = o1.strokeStyle;\r
+  }\r
+\r
+  function processStyle(styleString) {\r
+    var str, alpha = 1;\r
+\r
+    styleString = String(styleString);\r
+    if (styleString.substring(0, 3) == "rgb") {\r
+      var start = styleString.indexOf("(", 3);\r
+      var end = styleString.indexOf(")", start + 1);\r
+      var guts = styleString.substring(start + 1, end).split(",");\r
+\r
+      str = "#";\r
+      for (var i = 0; i < 3; i++) {\r
+        str += dec2hex[parseInt(guts[i])];\r
+      }\r
+\r
+      if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {\r
+        alpha = guts[3];\r
+      }\r
+    } else {\r
+      str = styleString;\r
+    }\r
+\r
+    return [str, alpha];\r
+  }\r
+\r
+  function processLineCap(lineCap) {\r
+    switch (lineCap) {\r
+      case "butt":\r
+        return "flat";\r
+      case "round":\r
+        return "round";\r
+      case "square":\r
+      default:\r
+        return "square";\r
+    }\r
+  }\r
+\r
+  /**\r
+   * This class implements CanvasRenderingContext2D interface as described by\r
+   * the WHATWG.\r
+   * @param {HTMLElement} surfaceElement The element that the 2D context should\r
+   * be associated with\r
+   */\r
+   function CanvasRenderingContext2D_(surfaceElement) {\r
+    this.m_ = createMatrixIdentity();\r
+\r
+    this.mStack_ = [];\r
+    this.aStack_ = [];\r
+    this.currentPath_ = [];\r
+\r
+    // Canvas context properties\r
+    this.strokeStyle = "#000";\r
+    this.fillStyle = "#ccc";\r
+\r
+    this.lineWidth = 1;\r
+    this.lineJoin = "miter";\r
+    this.lineCap = "butt";\r
+    this.miterLimit = 10;\r
+    this.globalAlpha = 1;\r
+\r
+    var el = document.createElement('div');\r
+    el.style.width =  surfaceElement.clientWidth + 'px';\r
+    el.style.height = surfaceElement.clientHeight + 'px';\r
+    el.style.overflow = 'hidden';\r
+    el.style.position = 'absolute';\r
+    surfaceElement.appendChild(el);\r
+\r
+    this.element_ = el;\r
+  };\r
+\r
+  var contextPrototype = CanvasRenderingContext2D_.prototype;\r
+  contextPrototype.clearRect = function() {\r
+    this.element_.innerHTML = "";\r
+    this.currentPath_ = [];\r
+  };\r
+\r
+  contextPrototype.beginPath = function() {\r
+    // TODO: Branch current matrix so that save/restore has no effect\r
+    //       as per safari docs.\r
+\r
+    this.currentPath_ = [];\r
+  };\r
+\r
+  contextPrototype.moveTo = function(aX, aY) {\r
+    this.currentPath_.push({type: "moveTo", x: aX, y: aY});\r
+  };\r
+\r
+  contextPrototype.lineTo = function(aX, aY) {\r
+    this.currentPath_.push({type: "lineTo", x: aX, y: aY});\r
+  };\r
+\r
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,\r
+                                            aCP2x, aCP2y,\r
+                                            aX, aY) {\r
+    this.currentPath_.push({type: "bezierCurveTo",\r
+                           cp1x: aCP1x,\r
+                           cp1y: aCP1y,\r
+                           cp2x: aCP2x,\r
+                           cp2y: aCP2y,\r
+                           x: aX,\r
+                           y: aY});\r
+  };\r
+\r
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {\r
+    // VML's qb produces different output to Firefox's\r
+    // FF's behaviour seems to have changed in 1.5.0.1, check this\r
+    this.bezierCurveTo(aCPx, aCPy, aCPx, aCPy, aX, aY);\r
+  };\r
+\r
+  contextPrototype.arc = function(aX, aY, aRadius,\r
+                                  aStartAngle, aEndAngle, aClockwise) {\r
+    if (!aClockwise) {\r
+      var t = aStartAngle;\r
+      aStartAngle = aEndAngle;\r
+      aEndAngle = t;\r
+    }\r
+\r
+    aRadius *= 10;\r
+\r
+    var xStart = aX + (mc(aStartAngle) * aRadius) - 5;\r
+    var yStart = aY + (ms(aStartAngle) * aRadius) - 5;\r
+\r
+    var xEnd = aX + (mc(aEndAngle) * aRadius) - 5;\r
+    var yEnd = aY + (ms(aEndAngle) * aRadius) - 5;\r
+\r
+    this.currentPath_.push({type: "arc",\r
+                           x: aX,\r
+                           y: aY,\r
+                           radius: aRadius,\r
+                           xStart: xStart,\r
+                           yStart: yStart,\r
+                           xEnd: xEnd,\r
+                           yEnd: yEnd});\r
+\r
+  };\r
+\r
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {\r
+    this.moveTo(aX, aY);\r
+    this.lineTo(aX + aWidth, aY);\r
+    this.lineTo(aX + aWidth, aY + aHeight);\r
+    this.lineTo(aX, aY + aHeight);\r
+    this.closePath();\r
+  };\r
+\r
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {\r
+    // Will destroy any existing path (same as FF behaviour)\r
+    this.beginPath();\r
+    this.moveTo(aX, aY);\r
+    this.lineTo(aX + aWidth, aY);\r
+    this.lineTo(aX + aWidth, aY + aHeight);\r
+    this.lineTo(aX, aY + aHeight);\r
+    this.closePath();\r
+    this.stroke();\r
+  };\r
+\r
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {\r
+    // Will destroy any existing path (same as FF behaviour)\r
+    this.beginPath();\r
+    this.moveTo(aX, aY);\r
+    this.lineTo(aX + aWidth, aY);\r
+    this.lineTo(aX + aWidth, aY + aHeight);\r
+    this.lineTo(aX, aY + aHeight);\r
+    this.closePath();\r
+    this.fill();\r
+  };\r
+\r
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {\r
+    var gradient = new CanvasGradient_("gradient");\r
+    return gradient;\r
+  };\r
+\r
+  contextPrototype.createRadialGradient = function(aX0, aY0,\r
+                                                   aR0, aX1,\r
+                                                   aY1, aR1) {\r
+    var gradient = new CanvasGradient_("gradientradial");\r
+    gradient.radius1_ = aR0;\r
+    gradient.radius2_ = aR1;\r
+    gradient.focus_.x = aX0;\r
+    gradient.focus_.y = aY0;\r
+    return gradient;\r
+  };\r
+\r
+  contextPrototype.drawImage = function (image, var_args) {\r
+    var dx, dy, dw, dh, sx, sy, sw, sh;\r
+    var w = image.width;\r
+    var h = image.height;\r
+\r
+    if (arguments.length == 3) {\r
+      dx = arguments[1];\r
+      dy = arguments[2];\r
+      sx = sy = 0;\r
+      sw = dw = w;\r
+      sh = dh = h;\r
+    } else if (arguments.length == 5) {\r
+      dx = arguments[1];\r
+      dy = arguments[2];\r
+      dw = arguments[3];\r
+      dh = arguments[4];\r
+      sx = sy = 0;\r
+      sw = w;\r
+      sh = h;\r
+    } else if (arguments.length == 9) {\r
+      sx = arguments[1];\r
+      sy = arguments[2];\r
+      sw = arguments[3];\r
+      sh = arguments[4];\r
+      dx = arguments[5];\r
+      dy = arguments[6];\r
+      dw = arguments[7];\r
+      dh = arguments[8];\r
+    } else {\r
+      throw "Invalid number of arguments";\r
+    }\r
+\r
+    var d = this.getCoords_(dx, dy);\r
+\r
+    var w2 = (sw / 2);\r
+    var h2 = (sh / 2);\r
+\r
+    var vmlStr = [];\r
+\r
+    // For some reason that I've now forgotten, using divs didn't work\r
+    vmlStr.push(' <g_vml_:group',\r
+                ' coordsize="1000,1000"',\r
+                ' coordorigin="0, 0"' ,\r
+                ' style="width:100px;height:100px;position:absolute;');\r
+\r
+    // If filters are necessary (rotation exists), create them\r
+    // filters are bog-slow, so only create them if abbsolutely necessary\r
+    // The following check doesn't account for skews (which don't exist\r
+    // in the canvas spec (yet) anyway.\r
+\r
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {\r
+      var filter = [];\r
+\r
+      // Note the 12/21 reversal\r
+      filter.push("M11='", this.m_[0][0], "',",\r
+                  "M12='", this.m_[1][0], "',",\r
+                  "M21='", this.m_[0][1], "',",\r
+                  "M22='", this.m_[1][1], "',",\r
+                  "Dx='", d.x, "',",\r
+                  "Dy='", d.y, "'");\r
+\r
+      // Bounding box calculation (need to minimize displayed area so that\r
+      // filters don't waste time on unused pixels.\r
+      var max = d;\r
+      var c2 = this.getCoords_(dx+dw, dy);\r
+      var c3 = this.getCoords_(dx, dy+dh);\r
+      var c4 = this.getCoords_(dx+dw, dy+dh);\r
+\r
+      max.x = Math.max(max.x, c2.x, c3.x, c4.x);\r
+      max.y = Math.max(max.y, c2.y, c3.y, c4.y);\r
+\r
+      vmlStr.push(" padding:0 ", mr(max.x), "px ", mr(max.y),\r
+                  "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",\r
+                  filter.join(""), ", sizingmethod='clip');")\r
+    } else {\r
+      vmlStr.push(" top:", d.y, "px;left:", d.x, "px;")\r
+    }\r
+\r
+    vmlStr.push(' ">' ,\r
+                '<g_vml_:image src="', image.src, '"',\r
+                ' style="width:', dw, ';',\r
+                ' height:', dh, ';"',\r
+                ' cropleft="', sx / w, '"',\r
+                ' croptop="', sy / h, '"',\r
+                ' cropright="', (w - sx - sw) / w, '"',\r
+                ' cropbottom="', (h - sy - sh) / h, '"',\r
+                ' />',\r
+                '</g_vml_:group>');\r
+\r
+    this.element_.insertAdjacentHTML("BeforeEnd",\r
+                                    vmlStr.join(""));\r
+  };\r
+\r
+  contextPrototype.stroke = function(aFill) {\r
+    var lineStr = [];\r
+    var lineOpen = false;\r
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);\r
+    var color = a[0];\r
+    var opacity = a[1] * this.globalAlpha;\r
+\r
+    lineStr.push('<g_vml_:shape',\r
+                 ' fillcolor="', color, '"',\r
+                 ' filled="', Boolean(aFill), '"',\r
+                 ' style="position:absolute;width:10;height:10;"',\r
+                 ' coordorigin="0 0" coordsize="100 100"',\r
+                 ' stroked="', !aFill, '"',\r
+                 ' strokeweight="', this.lineWidth, '"',\r
+                 ' strokecolor="', color, '"',\r
+                 ' path="');\r
+\r
+    var newSeq = false;\r
+    var min = {x: null, y: null};\r
+    var max = {x: null, y: null};\r
+\r
+    for (var i = 0; i < this.currentPath_.length; i++) {\r
+      var p = this.currentPath_[i];\r
+\r
+      if (p.type == "moveTo") {\r
+        lineStr.push(" m ");\r
+        var c = this.getCoords_(p.x, p.y);\r
+        lineStr.push(mr(c.x), ",", mr(c.y));\r
+      } else if (p.type == "lineTo") {\r
+        lineStr.push(" l ");\r
+        var c = this.getCoords_(p.x, p.y);\r
+        lineStr.push(mr(c.x), ",", mr(c.y));\r
+      } else if (p.type == "close") {\r
+        lineStr.push(" x ");\r
+      } else if (p.type == "bezierCurveTo") {\r
+        lineStr.push(" c ");\r
+        var c = this.getCoords_(p.x, p.y);\r
+        var c1 = this.getCoords_(p.cp1x, p.cp1y);\r
+        var c2 = this.getCoords_(p.cp2x, p.cp2y);\r
+        lineStr.push(mr(c1.x), ",", mr(c1.y), ",",\r
+                     mr(c2.x), ",", mr(c2.y), ",",\r
+                     mr(c.x), ",", mr(c.y));\r
+      } else if (p.type == "arc") {\r
+        lineStr.push(" ar ");\r
+        var c  = this.getCoords_(p.x, p.y);\r
+        var cStart = this.getCoords_(p.xStart, p.yStart);\r
+        var cEnd = this.getCoords_(p.xEnd, p.yEnd);\r
+\r
+        // TODO: FIX (matricies (scale+rotation) now buggered this up)\r
+        //       VML arc also doesn't seem able to do rotated non-circular\r
+        //       arcs without parent grouping.\r
+        var absXScale = this.m_[0][0];\r
+        var absYScale = this.m_[1][1];\r
+\r
+        lineStr.push(mr(c.x - absXScale * p.radius), ",",\r
+                     mr(c.y - absYScale * p.radius), " ",\r
+                     mr(c.x + absXScale * p.radius), ",",\r
+                     mr(c.y + absYScale * p.radius), " ",\r
+                     mr(cStart.x), ",", mr(cStart.y), " ",\r
+                     mr(cEnd.x), ",", mr(cEnd.y));\r
+      }\r
+\r
+\r
+      // TODO: Following is broken for curves due to\r
+      //       move to proper paths.\r
+\r
+      // Figure out dimensions so we can do gradient fills\r
+      // properly\r
+      if(c) {\r
+        if (min.x == null || c.x < min.x) {\r
+          min.x = c.x;\r
+        }\r
+        if (max.x == null || c.x > max.x) {\r
+          max.x = c.x;\r
+        }\r
+        if (min.y == null || c.y < min.y) {\r
+          min.y = c.y;\r
+        }\r
+        if (max.y == null || c.y > max.y) {\r
+          max.y = c.y;\r
+        }\r
+      }\r
+    }\r
+    lineStr.push(' ">');\r
+\r
+    if (typeof this.fillStyle == "object") {\r
+      var focus = {x: "50%", y: "50%"};\r
+      var width = (max.x - min.x);\r
+      var height = (max.y - min.y);\r
+      var dimension = (width > height) ? width : height;\r
+\r
+      focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";\r
+      focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";\r
+\r
+      var colors = [];\r
+\r
+      // inside radius (%)\r
+      if (this.fillStyle.type_ == "gradientradial") {\r
+        var inside = (this.fillStyle.radius1_ / dimension * 100);\r
+\r
+        // percentage that outside radius exceeds inside radius\r
+        var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;\r
+      } else {\r
+        var inside = 0;\r
+        var expansion = 100;\r
+      }\r
+\r
+      var insidecolor = {offset: null, color: null};\r
+      var outsidecolor = {offset: null, color: null};\r
+\r
+      // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie\r
+      // won't interpret it correctly\r
+      this.fillStyle.colors_.sort(function (cs1, cs2) {\r
+        return cs1.offset - cs2.offset;\r
+      });\r
+\r
+      for (var i = 0; i < this.fillStyle.colors_.length; i++) {\r
+        var fs = this.fillStyle.colors_[i];\r
+\r
+        colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");\r
+\r
+        if (fs.offset > insidecolor.offset || insidecolor.offset == null) {\r
+          insidecolor.offset = fs.offset;\r
+          insidecolor.color = fs.color;\r
+        }\r
+\r
+        if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {\r
+          outsidecolor.offset = fs.offset;\r
+          outsidecolor.color = fs.color;\r
+        }\r
+      }\r
+      colors.pop();\r
+\r
+      lineStr.push('<g_vml_:fill',\r
+                   ' color="', outsidecolor.color, '"',\r
+                   ' color2="', insidecolor.color, '"',\r
+                   ' type="', this.fillStyle.type_, '"',\r
+                   ' focusposition="', focus.x, ', ', focus.y, '"',\r
+                   ' colors="', colors.join(""), '"',\r
+                   ' opacity="', opacity, '" />');\r
+    } else if (aFill) {\r
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');\r
+    } else {\r
+      lineStr.push(\r
+        '<g_vml_:stroke',\r
+        ' opacity="', opacity,'"',\r
+        ' joinstyle="', this.lineJoin, '"',\r
+        ' miterlimit="', this.miterLimit, '"',\r
+        ' endcap="', processLineCap(this.lineCap) ,'"',\r
+        ' weight="', this.lineWidth, 'px"',\r
+        ' color="', color,'" />'\r
+      );\r
+    }\r
+\r
+    lineStr.push("</g_vml_:shape>");\r
+\r
+    this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));\r
+    \r
+    this.currentPath_ = [];\r
+  };\r
+\r
+  contextPrototype.fill = function() {\r
+    this.stroke(true);\r
+  }\r
+\r
+  contextPrototype.closePath = function() {\r
+    this.currentPath_.push({type: "close"});\r
+  };\r
+\r
+  /**\r
+   * @private\r
+   */\r
+  contextPrototype.getCoords_ = function(aX, aY) {\r
+    return {\r
+      x: 10 * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - 5,\r
+      y: 10 * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - 5\r
+    }\r
+  };\r
+\r
+  contextPrototype.save = function() {\r
+    var o = {};\r
+    copyState(this, o);\r
+    this.aStack_.push(o);\r
+    this.mStack_.push(this.m_);\r
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);\r
+  };\r
+\r
+  contextPrototype.restore = function() {\r
+    copyState(this.aStack_.pop(), this);\r
+    this.m_ = this.mStack_.pop();\r
+  };\r
+\r
+  contextPrototype.translate = function(aX, aY) {\r
+    var m1 = [\r
+      [1,  0,  0],\r
+      [0,  1,  0],\r
+      [aX, aY, 1]\r
+    ];\r
+\r
+    this.m_ = matrixMultiply(m1, this.m_);\r
+  };\r
+\r
+  contextPrototype.rotate = function(aRot) {\r
+    var c = mc(aRot);\r
+    var s = ms(aRot);\r
+\r
+    var m1 = [\r
+      [c,  s, 0],\r
+      [-s, c, 0],\r
+      [0,  0, 1]\r
+    ];\r
+\r
+    this.m_ = matrixMultiply(m1, this.m_);\r
+  };\r
+\r
+  contextPrototype.scale = function(aX, aY) {\r
+    var m1 = [\r
+      [aX, 0,  0],\r
+      [0,  aY, 0],\r
+      [0,  0,  1]\r
+    ];\r
+\r
+    this.m_ = matrixMultiply(m1, this.m_);\r
+  };\r
+\r
+  /******** STUBS ********/\r
+  contextPrototype.clip = function() {\r
+    // TODO: Implement\r
+  };\r
+\r
+  contextPrototype.arcTo = function() {\r
+    // TODO: Implement\r
+  };\r
+\r
+  contextPrototype.createPattern = function() {\r
+    return new CanvasPattern_;\r
+  };\r
+\r
+  // Gradient / Pattern Stubs\r
+  function CanvasGradient_(aType) {\r
+    this.type_ = aType;\r
+    this.radius1_ = 0;\r
+    this.radius2_ = 0;\r
+    this.colors_ = [];\r
+    this.focus_ = {x: 0, y: 0};\r
+  }\r
+\r
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {\r
+    aColor = processStyle(aColor);\r
+    this.colors_.push({offset: 1-aOffset, color: aColor});\r
+  };\r
+\r
+  function CanvasPattern_() {}\r
+\r
+  // set up externs\r
+  G_vmlCanvasManager = G_vmlCanvasManager_;\r
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;\r
+  CanvasGradient = CanvasGradient_;\r
+  CanvasPattern = CanvasPattern_;\r
+\r
+})();\r
+\r
+} // if\r
diff --git a/pub/qdb/res/themes/default/js/graph.js b/pub/qdb/res/themes/default/js/graph.js
new file mode 100644 (file)
index 0000000..0903618
--- /dev/null
@@ -0,0 +1,594 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# graph.js                                                                    #\r
+# Turns graph data into nice graphs                                           #\r
+###############################################################################\r
+# $Id:: graph.js 308 2007-02-09 01:31:25Z ceetee                            $ #\r
+###############################################################################\r
+*/\r
+\r
+var graphConfig = new Array();\r
+graphConfig["bar_chart_values"] = 5;\r
+graphConfig["pie_chart_radius"] = 175;\r
+graphConfig["pie_chart_extrusion"] = 5;\r
+graphConfig["pie_chart_colors"] = new Array(\r
+       "#DCDCDC", "#CCCCCC", "#BCBCBC", "#ACACAC"\r
+);\r
+graphConfig["pie_chart_border_width"] = 1;\r
+graphConfig["pie_chart_border_color"] = "#ACACAC";\r
+graphConfig["pie_chart_random_rotation"] = false;\r
+graphConfig["ogive_values"] = 5;\r
+graphConfig["ogive_chart_width"] = 660;\r
+graphConfig["ogive_chart_height"] = 360;\r
+graphConfig["ogive_chart_color"] = "#DCDCDC";\r
+graphConfig["ogive_average_color"] = "#BCBCBC";\r
+graphConfig["decimal_point_is_comma"] = false;\r
+graphConfig["average_decimal_count"] = 2;\r
+\r
+function checkForGraphs () {\r
+       var dls = document.getElementsByTagName("dl");\r
+       for (var i = dls.length - 1; i >= 0; i--) {\r
+               var dl = dls[i];\r
+               var func;\r
+               if (hasClassName(dl, "bar-chart-data")) {\r
+                       func = createBarChart;\r
+               }\r
+               else if (hasClassName(dl, "pie-chart-data")) {\r
+                       func = createPieChart;\r
+               }\r
+               else if (hasClassName(dl, "ogive-data")) {\r
+                       func = createOgive;\r
+               }\r
+               if (func) {\r
+                       var chartData = extractChartData(dl);\r
+                       var samples = extractChartLabelCount(dl);\r
+                       var node = func(dl, chartData, samples);\r
+                       node.id = dl.id;\r
+               }\r
+       }\r
+}\r
+\r
+function createBarChart (sourceNode, chartData, samples) {\r
+       var div = document.createElement("div");\r
+       div.className = "chart bar-chart";\r
+       var graph = document.createElement("div");\r
+       graph.className = "bar-chart-graph";\r
+       div.appendChild(graph);\r
+       var max = 0;\r
+       for (var i = 0; i < chartData.length; i++) {\r
+               var value = chartData[i][1];\r
+               if (value > max) {\r
+                       max = value;\r
+               }\r
+       }\r
+       createChartPane(div, graph, chartData, samples,\r
+               graphConfig["bar_chart_values"], 0, max);\r
+       var barWidth = 100 / chartData.length;\r
+       var total = 0;\r
+       for (var i = 0; i < chartData.length; i++) {\r
+               var data = chartData[i];\r
+               var text = data[0];\r
+               var value = data[1];\r
+               var column = document.createElement("div");\r
+               column.className = "bar-chart-column";\r
+               column.style.left = i * barWidth + "%";\r
+               column.style.width = barWidth + "%";\r
+               column.title = data[0] + " "\r
+                       + String.fromCharCode(0x2192) + " " + data[1];\r
+               if (value > 0) {\r
+                       var bar = document.createElement("div");\r
+                       bar.className = "bar-chart-bar";\r
+                       bar.style.height = Math.round(100 * value / max) + "%";\r
+                       var innerBar = document.createElement("div");\r
+                       innerBar.className = "bar-chart-inner-bar";\r
+                       bar.appendChild(innerBar);\r
+                       column.appendChild(bar);\r
+               }\r
+               graph.appendChild(column);\r
+               total += value;\r
+       }\r
+       var avg = total / chartData.length;\r
+       var avgDiv = document.createElement("div");\r
+       avgDiv.className = "bar-chart-average-container";\r
+       avgDiv.style.top = (100 - (100 * avg / max)) + "%";\r
+       var stdDevTotal = 0;\r
+       for (var i = 0; i < chartData.length; i++) {\r
+               var temp = chartData[i][1] - avg;\r
+               stdDevTotal += temp * temp;\r
+       }\r
+       var stdDev = roundToDecimals(\r
+               Math.sqrt(stdDevTotal / chartData.length),\r
+               graphConfig["average_decimal_count"]);\r
+       var avgText = roundToDecimals(avg, graphConfig["average_decimal_count"]);\r
+       if (graphConfig["decimal_point_is_comma"]) {\r
+               avgText = ("" + avgText).replace(".", ",");\r
+               stdDev = ("" + stdDev).replace(".", ",");\r
+       }\r
+       var avgSpan = document.createElement("span");\r
+       avgSpan.appendChild(document.createTextNode(avgText));\r
+       avgSpan.className = "bar-chart-average";\r
+       var stdDevSpan = document.createElement("span");\r
+       stdDevSpan.appendChild(\r
+               document.createTextNode(String.fromCharCode(0x00B1) + " " + stdDev));\r
+       stdDevSpan.className = "bar-chart-standard-deviation";\r
+       var avgTextDiv = document.createElement("div");\r
+       avgTextDiv.appendChild(avgSpan);\r
+       avgTextDiv.appendChild(document.createTextNode(" "));\r
+       avgTextDiv.appendChild(stdDevSpan);\r
+       avgDiv.appendChild(avgTextDiv);\r
+       graph.appendChild(avgDiv);\r
+       sourceNode.parentNode.replaceChild(div, sourceNode);\r
+       return div;\r
+}\r
+\r
+function createPieChart (sourceNode, chartData) {\r
+       var cnv = document.createElement("canvas");\r
+       var rad = graphConfig["pie_chart_radius"];\r
+       var ext = graphConfig["pie_chart_extrusion"];\r
+       var side = (rad + ext) * 2;\r
+       cnv.width = cnv.height = side;\r
+       cnv.style.width = cnv.style.height = side + "px";\r
+       var graph = document.createElement("div");\r
+       graph.appendChild(cnv);\r
+       cnv = ensureCanvas(cnv);\r
+       if (!cnv) {\r
+               return createBarChart(sourceNode, chartData);\r
+       }\r
+       var div = document.createElement("div");\r
+       div.className = "chart pie-chart";\r
+       sourceNode.parentNode.replaceChild(div, sourceNode);\r
+       graph.className = "pie-chart-graph";\r
+       var legend = document.createElement("dl");\r
+       legend.className = "pie-chart-legend";\r
+       div.appendChild(graph);\r
+       div.appendChild(legend);\r
+       drawPieChart(cnv, legend, chartData);\r
+       return div;\r
+}\r
+\r
+function drawPieChart (canvas, legend, data) {\r
+       var ctx = canvas.getContext("2d");\r
+       var total = 0;\r
+       for (var i = 0; i < data.length; i++) {\r
+               total += data[i][1];\r
+       }\r
+       var runningTotal = 0;\r
+       var rad = graphConfig["pie_chart_radius"];\r
+       var ext = graphConfig["pie_chart_extrusion"];\r
+       var xCenter = rad + ext;\r
+       var yCenter = rad + ext;\r
+       var radius = rad;\r
+       var colors = graphConfig["pie_chart_colors"];\r
+       var stroke;\r
+       if (graphConfig["pie_chart_border_width"]) {\r
+               ctx.strokeStyle = graphConfig["pie_chart_border_color"];\r
+               var w = graphConfig["pie_chart_border_width"];\r
+               ctx.lineWidth = w;\r
+               radius -= 2 * w;\r
+               stroke = true;\r
+       }\r
+       else {\r
+               stroke = false;\r
+       }\r
+       var initialAngle = (graphConfig["pie_chart_random_rotation"]\r
+               ? Math.round(2 * Math.PI * Math.random())\r
+               : - Math.PI / 2);\r
+       for (var i = 0; i < data.length; i++) {\r
+               var name = data[i][0];\r
+               var value = data[i][1];\r
+               var color = colors[i % colors.length];\r
+               if (value > 0) {\r
+                       var startAngle = runningTotal / total * 2 * Math.PI + initialAngle;\r
+                       runningTotal += value;\r
+                       var endAngle = runningTotal / total * 2 * Math.PI + initialAngle;\r
+                       var diff = (startAngle + endAngle) / 2;\r
+                       var x = xCenter + Math.cos(diff) * ext;\r
+                       var y = yCenter + Math.sin(diff) * ext;\r
+                       ctx.fillStyle = color;\r
+                       ctx.beginPath();\r
+                       ctx.moveTo(x, y);\r
+                       ctx.arc(x, y, radius, startAngle, endAngle, false);\r
+                       ctx.lineTo(x, y);\r
+                       ctx.closePath();\r
+                       ctx.fill();\r
+                       if (stroke) {\r
+                               // Safari doesn't like it when we don't recreate the path\r
+                               ctx.beginPath();\r
+                               ctx.moveTo(x, y);\r
+                               ctx.arc(x, y, radius, startAngle, endAngle, false);\r
+                               ctx.lineTo(x, y);\r
+                               ctx.closePath();\r
+                               ctx.stroke();\r
+                       }\r
+               }\r
+               var block = document.createElement("div");\r
+               block.style.backgroundColor = color;\r
+               var dt = document.createElement("dt");\r
+               dt.appendChild(block);\r
+               legend.appendChild(dt);\r
+               var dd = document.createElement("dd");\r
+               var l = document.createElement("span");\r
+               l.className = "label";\r
+               l.appendChild(document.createTextNode(name));\r
+               dd.appendChild(l);\r
+               var v = document.createElement("span");\r
+               v.className = "value";\r
+               v.appendChild(document.createTextNode(value));\r
+               dd.appendChild(v);\r
+               var p = document.createElement("span");\r
+               p.className = "percentage";\r
+               p.appendChild(document.createTextNode(\r
+                       (value == 0 ? 0 : Math.round(100 * value / total)) + "%"));\r
+               dd.appendChild(p);\r
+               legend.appendChild(dd);\r
+       }\r
+}\r
+\r
+function createOgive (sourceNode, chartData, samples) {\r
+       var cnv = document.createElement("canvas");\r
+       cnv.width = graphConfig["ogive_chart_width"];\r
+       cnv.height = graphConfig["ogive_chart_height"];\r
+       cnv.style.width = graphConfig["ogive_chart_width"] + "px";\r
+       cnv.style.height = graphConfig["ogive_chart_height"] + "px";\r
+       var graph = document.createElement("div");\r
+       graph.appendChild(cnv);\r
+       cnv = ensureCanvas(cnv);\r
+       if (!cnv) {\r
+               var total = 0;\r
+               for (var i = 0; i < chartData.length; i++) {\r
+                       var old = chartData[i][1];\r
+                       chartData[i][1] += total;\r
+                       total += old;\r
+               }\r
+               return createBarChart(sourceNode, chartData, samples);\r
+       }\r
+       var div = document.createElement("div");\r
+       div.className = "chart ogive";\r
+       div.appendChild(graph);\r
+       sourceNode.parentNode.replaceChild(div, sourceNode);\r
+       graph.className = "ogive-graph";\r
+       var ignoreFirst = extractOgiveIgnoreCount(sourceNode);\r
+       var totalIgnored = 0;\r
+       if (ignoreFirst) {\r
+               for (var i = 0; i < ignoreFirst; i++) {\r
+                       totalIgnored += chartData[i][1];\r
+               }\r
+               var newChartData = new Array();\r
+               for (var i = ignoreFirst; i < chartData.length; i++) {\r
+                       newChartData[i - ignoreFirst] = chartData[i];\r
+               }\r
+               chartData = newChartData;\r
+       }\r
+       var chartCumulData = new Array();\r
+       var total = 0;\r
+       for (var i = 0; i < chartData.length; i++) {\r
+               total += chartData[i][1];\r
+               chartCumulData[i] = total;\r
+       }\r
+       var width = graphConfig["ogive_chart_width"];\r
+       var max = chartCumulData[chartCumulData.length - 1];\r
+       var xy = new Array();\r
+       for (var i = 0; i < chartCumulData.length; i++) {\r
+               xy[i + 1] = chartCumulData[i];\r
+       }\r
+       var regrParam = powerRegression(xy);\r
+       var a = regrParam[0];\r
+       var b = regrParam[1];\r
+       var drawAvg;\r
+       if (!isNaN(a) && !isNaN(b)) {\r
+               var chartAvgData = new Array();\r
+               for (var x = 0; x < width; x++) {\r
+                       var i = x / width * chartData.length;\r
+                       chartAvgData[x] = a * Math.pow(i, b);\r
+                       if (chartAvgData[x] > max) {\r
+                               max = chartAvgData[x];\r
+                       }\r
+               }\r
+               drawAvg = true;\r
+       }\r
+       else {\r
+               drawAvg = false;\r
+       }\r
+       createChartPane(div, graph, chartData, samples,\r
+               graphConfig["ogive_values"], 0, max, totalIgnored);\r
+               var scale = graphConfig["ogive_chart_height"] / max;\r
+       drawOgive(cnv, chartCumulData, false, scale);\r
+       if (drawAvg) {\r
+               drawOgive(cnv, chartAvgData, true, scale);\r
+               var equation = document.createElement("div");\r
+               equation.className = "regression-equation";\r
+               equation.appendChild(document.createTextNode("y = "));\r
+               var aRounded = roundToDecimals(a, 2);\r
+               var bRounded = roundToDecimals(b, 2);\r
+               if (graphConfig["decimal_point_is_comma"]) {\r
+                       aRounded = ("" + aRounded).replace(".", ",");\r
+                       bRounded = ("" + bRounded).replace(".", ",");\r
+               }\r
+               if (aRounded != 1) {\r
+                       var aTxt = document.createElement("span");\r
+                       aTxt.appendChild(document.createTextNode(aRounded));\r
+                       equation.appendChild(aTxt);\r
+               }\r
+               equation.appendChild(document.createTextNode("x"));\r
+               if (bRounded != 1) {\r
+                       var bTxt = document.createElement("sup");\r
+                       bTxt.appendChild(document.createTextNode(bRounded));\r
+                       equation.appendChild(bTxt);\r
+               }\r
+               equation.style.position = "absolute";\r
+               var xPos = 3/4;\r
+               var xPad = 15;\r
+               var yBase = Math.round(chartAvgData[Math.round(xPos * chartAvgData.length)]\r
+                       / max * graphConfig["ogive_chart_height"]);\r
+               /*if (b < 1) {*/\r
+                       equation.style.left = xPad + Math.round(\r
+                               xPos * graphConfig["ogive_chart_width"]) + "px";\r
+                       equation.style.bottom = yBase + "px";\r
+               /*}\r
+               else {\r
+                       equation.style.right = xPad + Math.round(\r
+                               (1 - xPos) * graphConfig["ogive_chart_width"]) + "px";\r
+                       equation.style.top = graphConfig["ogive_chart_height"] - yBase + "px";\r
+               }*/\r
+               div.appendChild(equation);\r
+       }\r
+       return div;\r
+}\r
+\r
+function drawOgive (canvas, chartData, avg, yScale) {\r
+       var ctx = canvas.getContext("2d");\r
+       var graphWidth = graphConfig["ogive_chart_width"];\r
+       var graphHeight = graphConfig["ogive_chart_height"];\r
+       if (avg) {\r
+               ctx.strokeStyle = graphConfig["ogive_average_color"];\r
+               ctx.lineWidth = 1;\r
+       }\r
+       else {\r
+               ctx.fillStyle = graphConfig["ogive_chart_color"];\r
+       }\r
+       var xScale = graphWidth / chartData.length;\r
+       var x0 = 0;\r
+       var y0 = graphHeight;\r
+       ctx.beginPath();\r
+       var notFirst = false;\r
+       for (var i = 0; i < chartData.length; i++) {\r
+               if (chartData[i] == null) continue;\r
+               var x = Math.round(x0 + (i + 1) * xScale);\r
+               var y = Math.round(y0 - chartData[i] * yScale);\r
+               if (notFirst) {\r
+                       ctx.lineTo(x, y);\r
+               }\r
+               else {\r
+                       ctx.moveTo(x, y);\r
+                       notFirst = true;\r
+               }\r
+       }\r
+       if (avg) {\r
+               ctx.stroke();\r
+       }\r
+       else {\r
+               ctx.lineTo(graphWidth, graphHeight);\r
+               ctx.lineTo(x0, y0);\r
+               ctx.fill();\r
+       }\r
+       return yScale;\r
+}\r
+\r
+function extractChartData (dl) {\r
+       var name, label;\r
+       var data = new Array();\r
+       for (var i = 0; i < dl.childNodes.length; i++) {\r
+               var child = dl.childNodes[i];\r
+               var eln = child.nodeName.toLowerCase();\r
+               switch (eln) {\r
+                       case "dt":\r
+                               name = child.firstChild.nodeValue;\r
+                               label = (child.title ? child.title : null);\r
+                               break;\r
+                       case "dd":\r
+                               var value = parseInt(child.firstChild.nodeValue);\r
+                               data.push(new Array(name, value, label));\r
+                               break;\r
+               }\r
+       }\r
+       return data;\r
+}\r
+\r
+function extractChartLabelCount (dl) {\r
+       var result = extractChartParameter(dl, "label-count");\r
+       if (result == null) return 0;\r
+       return result;\r
+}\r
+\r
+function extractOgiveIgnoreCount (dl) {\r
+       var result = extractChartParameter(dl, "ignore-first");\r
+       if (result == null) return 0;\r
+       return result;\r
+}\r
+\r
+function extractChartParameter (dl, name) {\r
+       name += "-";\r
+       var classNames = dl.className.split(/\s+/);\r
+       for (var i = 0; i < classNames.length; i++) {\r
+               var className = classNames[i];\r
+               if (className.indexOf(name) == 0) {\r
+                       return parseInt(className.substring(name.length));\r
+               }\r
+       }\r
+       return null;\r
+}\r
+\r
+function createChartPane (div, graph, chartData, samples, values, min, max, delta) {\r
+       if (!delta) delta = 0;\r
+       div.appendChild(createChartValues(min + delta, max + delta, values, graph, delta));\r
+       div.appendChild(createChartLabels(chartData, samples));\r
+}\r
+\r
+function createChartValues (min, max, count, graph, delta) {\r
+       var values = document.createElement("div");\r
+       values.className = "chart-values";\r
+       var top = createChartValue(max);\r
+       top.style.top = "0";\r
+       top.style.marginTop = "0";\r
+       var bottom = createChartValue(min);\r
+       bottom.style.bottom = "0";\r
+       bottom.style.marginBottom = "0";\r
+       values.appendChild(top);\r
+       var step = (max - min) / (count - 1);\r
+       for (var i = 1; i < count - 1; i++) {\r
+               var val = Math.round(step * i) + delta;\r
+               var perc = 100 / (count - 1) * i + "%";\r
+               var v = createChartValue(val);\r
+               v.style.bottom = perc;\r
+               values.appendChild(v);\r
+               var line = document.createElement("div");\r
+               line.className = "chart-line";\r
+               line.style.top = perc;\r
+               graph.appendChild(line);\r
+       }\r
+       var line = document.createElement("div");\r
+       line.className = "chart-line";\r
+       line.style.top = "0";\r
+       graph.appendChild(line);\r
+       values.appendChild(bottom);\r
+       return values;\r
+}\r
+\r
+function createChartValue (value) {\r
+       var v = document.createElement("div");\r
+       v.className = "chart-value";\r
+       v.appendChild(document.createTextNode(Math.round(value)));\r
+       return v;\r
+}\r
+\r
+function createChartLabels (chartData, samples) {\r
+       var labels = document.createElement("div");\r
+       labels.className = "chart-labels";\r
+       var sample = (samples > 0 && chartData.length > samples);\r
+       var barWidth = 100 / chartData.length;\r
+       var sampleInterval, labelWidth;\r
+       if (sample) {\r
+               if (chartData.length < samples * 2) {\r
+                       samples = Math.ceil(chartData.length / 2);\r
+               }\r
+               sampleInterval = chartData.length / samples;\r
+               labelWidth = 100 / samples;\r
+               var firstLabel = createChartLabel(\r
+                       chartData[0], 0, labelWidth + "%",\r
+                       "left");\r
+               labels.appendChild(firstLabel);\r
+               var lastLabel = createChartLabel(\r
+                       chartData[chartData.length - 1],\r
+                       (100 - labelWidth) + "%", labelWidth + "%",\r
+                       "right");\r
+               labels.appendChild(lastLabel);\r
+       }\r
+       else {\r
+               labelWidth = barWidth;\r
+       }\r
+       var labelCount = 0;\r
+       for (var i = 0; i < chartData.length; i++) {\r
+               var data = chartData[i];\r
+               if (!sample || Math.floor((labelCount + 0.5) * sampleInterval) == i) {\r
+                       if (sample && (++labelCount == 1 || labelCount == samples)) continue;\r
+                       var left = ((i + 0.5) * barWidth - labelWidth / 2) + "%";\r
+                       var label = createChartLabel(data, left, labelWidth + "%", "center");\r
+                       labels.appendChild(label);\r
+               }\r
+       }\r
+       return labels;\r
+}\r
+\r
+function createChartLabel (data, position, width, align) {\r
+       var label = document.createElement("div");\r
+       label.className = "chart-label";\r
+       label.style.left = position;\r
+       label.style.width = width;\r
+       var innerLabel = document.createElement("div");\r
+       innerLabel.className = "chart-inner-label";\r
+       var text = (data[2] != null ? data[2] : data[0]);\r
+       innerLabel.appendChild(document.createTextNode(text));\r
+       innerLabel.style.textAlign = align;\r
+       label.appendChild(innerLabel);\r
+       return label;\r
+}\r
+\r
+function ensureCanvas (canvas) {\r
+       if (!canvas.getContext && useExCanvas()) {\r
+               canvas = G_vmlCanvasManager.initElement(canvas);\r
+       }\r
+       return (canvas.getContext ? canvas : null);\r
+}\r
+\r
+function useExCanvas () {\r
+       return (typeof G_vmlCanvasManager != "undefined");\r
+}\r
+\r
+function hasClassName (element, className) {\r
+       return element.className.match(new RegExp("\\b" + className + "\\b"));\r
+}\r
+\r
+function powerRegression (data) {\r
+       var n = data.length;\r
+       var sumX = 0;\r
+       var sumY = 0;\r
+       var sumXX = 0;\r
+       var sumXY = 0;\r
+       for (i in data) {\r
+               var x = Math.log(i);\r
+               var y = Math.log(data[i]);\r
+               sumX += x;\r
+               sumY += y;\r
+               sumXX += x * x;\r
+               sumXY += x * y;\r
+       }\r
+       var sxx = sumXX - (sumX * sumX) / n;\r
+       var sxy = sumXY - (sumX * sumY) / n;\r
+       var xbar = sumX / n;\r
+       var ybar = sumY / n;\r
+       var b = sxy / sxx;\r
+       var a = Math.pow(Math.exp(1), ybar - b * xbar);\r
+       var result = new Array();\r
+       result[0] = a;\r
+       result[1] = b;\r
+       return result;\r
+}\r
+\r
+function roundToDecimals (number, decimals) {\r
+       var factor = Math.pow(10, decimals);\r
+       return Math.round(number * factor) / factor;\r
+}\r
+\r
+function addOnloadFunction (f) {\r
+       if (window.onload != null) {\r
+               var old = window.onload;\r
+               window.onload = function (e) {\r
+                       old(e);\r
+                       f();\r
+               };\r
+       }\r
+       else {\r
+               window.onload = f;\r
+       }\r
+}\r
+\r
+addOnloadFunction(checkForGraphs);\r
diff --git a/pub/qdb/res/themes/default/js/live_rating.js b/pub/qdb/res/themes/default/js/live_rating.js
new file mode 100644 (file)
index 0000000..a9a63fa
--- /dev/null
@@ -0,0 +1,230 @@
+/*\r
+###############################################################################\r
+# Chirpy! 0.3, a quote management system                                      #\r
+# Copyright (C) 2005-2007 Tim De Pauw <ceetee@users.sourceforge.net>          #\r
+###############################################################################\r
+# This program is free software; you can redistribute it and/or modify it     #\r
+# under the terms of the GNU General Public License as published by the Free  #\r
+# Software Foundation; either version 2 of the License, or (at your option)   #\r
+# any later version.                                                          #\r
+#                                                                             #\r
+# This program is distributed in the hope that it will be useful, but WITHOUT #\r
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #\r
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #\r
+# more details.                                                               #\r
+#                                                                             #\r
+# You should have received a copy of the GNU General Public License along     #\r
+# with this program; if not, write to the Free Software Foundation, Inc., 51  #\r
+# Franklin St, Fifth Floor, Boston, MA  02110-1301  USA                       #\r
+###############################################################################\r
+\r
+###############################################################################\r
+# live_rating.js                                                              #\r
+# Enables rating and reporting of quotes without leaving the page             #\r
+###############################################################################\r
+# $Id:: live_rating.js 291 2007-02-05 21:24:46Z ceetee                      $ #\r
+###############################################################################\r
+*/\r
+\r
+var useBlinking = true;\r
+var confirmationTimeout = 3000;\r
+var errorTimeout = 10000;\r
+var connectionTimeout = 30000;\r
+var pollingInterval = 1000;\r
+var okText = (/MSIE/.test(navigator.userAgent) && !window.opera\r
+       ? "OK" : String.fromCharCode(0x2713));\r
+\r
+var requests = new Array();\r
+\r
+function QuoteActionRequest (url, id, isReport) {\r
+       var t = getTimestamp();\r
+       var req = this;\r
+       this.id = id;\r
+       if (requests[this.id]) requests[this.id].verbose = false;\r
+       requests[this.id] = this;\r
+       this.url = url + "&output=xml&ignore=" + t;\r
+       this.isReport = isReport;\r
+       this.verbose = true;\r
+       this.resultField = document.getElementById('quote-live-vote-result-' + id);\r
+       this.setResult(locale["processing"], true);\r
+       this.ajax = getAjaxObject();\r
+       this.ajax.onreadystatechange = function() { readyStateChanged(req); };\r
+       this.startTime = t;\r
+       this.interval = setInterval(\r
+               function () { checkRequestTime(req); }, pollingInterval);\r
+       this.ajax.open("POST", this.url, true);\r
+       this.ajax.send("");\r
+}\r
+\r
+QuoteActionRequest.prototype.setResult = function (text, blink, timeout) {\r
+       if (!this.verbose) return;\r
+       if (this.fadeTimeout) {\r
+               clearTimeout(this.fadeTimeout);\r
+               this.fadeTimeout = null;\r
+       }\r
+       setText(this.resultField, text);\r
+       if (useBlinking)\r
+               this.resultField.style.textDecoration = blink ? "blink" : "";\r
+       var field = this.resultField;\r
+       if (timeout)\r
+               this.fadeTimeout = setTimeout(\r
+                       function () { setText(field, ""); }, timeout);\r
+};\r
+\r
+function sendRating (anchor, id) {\r
+       if (!ajaxSupported()) return true;\r
+       new QuoteActionRequest(anchor.href, id, false);\r
+       var other;\r
+       if (anchor.id.indexOf("down") >= 0) {\r
+               other = document.getElementById(anchor.id.replace("down", "up"));\r
+       }\r
+       else {\r
+               other = document.getElementById(anchor.id.replace("up", "down"));\r
+       }\r
+       addClassName(anchor, "casted-vote");\r
+       removeClassName(other, "casted-vote");\r
+       return false;\r
+}\r
+\r
+function sendReport (anchor, id) {\r
+       if (!ajaxSupported()) return true;\r
+       new QuoteActionRequest(anchor.href, id, true);\r
+       return false;\r
+}\r
+\r
+function readyStateChanged (req) {\r
+       if (req.ajax.readyState != 4) return;\r
+       if (req.ajax.status == 200) {\r
+               if (req.isReport)\r
+                       reportRequestCompleted(req, processXML(req.ajax.responseXML));\r
+               else\r
+                       ratingRequestCompleted(req, processXML(req.ajax.responseXML));\r
+       }\r
+       else\r
+               req.setResult(locale["error"], false, errorTimeout);\r
+       removeRequest(req);\r
+}\r
+\r
+function ratingRequestCompleted (req, result) {\r
+       if (result) {\r
+               switch (result["status"]) {\r
+                       case "1":\r
+                               req.setResult("");\r
+                               setText(document.getElementById('quote-rating-' + req.id),\r
+                                       result["rating"]);\r
+                               var vc = document.getElementById('quote-vote-count-' + req.id);\r
+                               if (vc) setText(vc, result["votes"]);\r
+                               req.setResult(okText, false, confirmationTimeout);\r
+                               break;\r
+                       case "2":\r
+                               req.setResult(locale["error"], false, errorTimeout);\r
+                               if (req.verbose) alert(locale["already_rated_text"]);\r
+                               break;\r
+                       case "3":\r
+                               req.setResult(locale["error"], false, errorTimeout);\r
+                               if (req.verbose) alert(locale["limit_exceeded_text"]);\r
+                               break;\r
+                       case "4":\r
+                               req.setResult(locale["error"], false, errorTimeout);\r
+                               if (req.verbose) alert(locale["quote_not_found_text"]);\r
+                               break;\r
+                       case "5":\r
+                               req.setResult(locale["error"], false, errorTimeout);\r
+                               if (req.verbose) alert(locale["session_required_text"]);\r
+                               break;\r
+                       default:\r
+                               req.setResult(locale["error"], false, errorTimeout);\r
+                               break;\r
+               }\r
+       }\r
+       else\r
+               req.setResult(locale["error"], false, errorTimeout);\r
+}\r
+\r
+function reportRequestCompleted (req, result) {\r
+       if (result) {\r
+               switch (result["status"]) {\r
+                       case "1":\r
+                               req.setResult("");\r
+                               document.getElementById('quote-' + req.id).className\r
+                                       += " flagged";\r
+                               var rep = document.getElementById('quote-report-' + req.id);\r
+                               var el = document.createElement("span");\r
+                               var at = document.createAttribute("class");\r
+                               at.value = "quote-flagged";\r
+                               el.setAttributeNode(at);\r
+                               el.appendChild(document.createTextNode(\r
+                                       "[" + locale["flagged"] + "]"));\r
+                               rep.parentNode.replaceChild(el, rep);\r
+                               req.setResult(okText, false, confirmationTimeout);\r
+                               break;\r
+                       case "5":\r
+                               req.setResult(locale["error"], false, errorTimeout);\r
+                               if (req.verbose) alert(locale["session_required_text"]);\r
+                               break;\r
+                       default:\r
+                               req.setResult(locale["error"], false, errorTimeout);\r
+                               break;\r
+               }\r
+       }\r
+       else\r
+               req.setResult(locale["error"], false, errorTimeout);\r
+}\r
+\r
+function removeRequest (req) {\r
+       requests[req.id] = null;\r
+       clearInterval(req.interval);\r
+}\r
+\r
+function checkRequestTime (req) {\r
+       if (getTimestamp() - req.startTime >= connectionTimeout) {\r
+               clearInterval(req.interval);\r
+               req.setResult(locale["error"], false, errorTimeout);\r
+               if (req.verbose) alert(locale["timeout_text"]);\r
+       }\r
+}\r
+\r
+function processXML (xml) {\r
+       if (!xml || !xml.childNodes\r
+       || xml.childNodes[xml.childNodes.length - 1].nodeName != "result")\r
+               return null;\r
+       var a = new Array();\r
+       var root = xml.childNodes[xml.childNodes.length - 1];\r
+       for (var i = 0; i < root.childNodes.length; i++) {\r
+               var node = root.childNodes[i];\r
+               a[node.nodeName] = node.firstChild.nodeValue;\r
+       }\r
+       return a;\r
+}\r
+\r
+function getTimestamp () {\r
+       return (new Date()).getTime();\r
+}\r
+\r
+function setText (element, text) {\r
+       if (element.firstChild)\r
+               element.firstChild.nodeValue = text;\r
+       else\r
+               element.appendChild(document.createTextNode(text));\r
+}\r
+\r
+function addClassName (element, className) {\r
+       element.className += " " + className;\r
+}\r
+\r
+function removeClassName (element, className) {\r
+       var c = getClassNames(element);\r
+       if (c.length == 0) return;\r
+       var newCN = c[0];\r
+       for (var i = 1; i < c.length; c++) {\r
+               if (c[i] == className) continue;\r
+               newCN += " " + c[i];\r
+       }\r
+       element.className = newCN;\r
+}\r
+\r
+function getClassNames (element) {\r
+       if (element.className == null || element.className.length == 0)\r
+               return new Array();\r
+       return element.className.split(/ +/);\r
+}
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/slider/boxsizing.htc b/pub/qdb/res/themes/default/js/slider/boxsizing.htc
new file mode 100644 (file)
index 0000000..fbeaa56
--- /dev/null
@@ -0,0 +1,157 @@
+<component lightWeight="true">\r
+<attach event="onpropertychange" onevent="checkPropertyChange()" />\r
+<attach event="ondetach" onevent="restore()" />\r
+<script>\r
+//<![CDATA[\r
+\r
+var doc = element.document;\r
+\r
+function init() {\r
+       updateBorderBoxWidth();\r
+       updateBorderBoxHeight();\r
+}\r
+\r
+function restore() {\r
+       element.runtimeStyle.width = "";\r
+       element.runtimeStyle.height = "";\r
+}\r
+\r
+/* border width getters */\r
+function getBorderWidth(sSide) {\r
+       if (element.currentStyle["border" + sSide + "Style"] == "none")\r
+               return 0;\r
+       var n = parseInt(element.currentStyle["border" + sSide + "Width"]);\r
+       return n || 0;\r
+}\r
+\r
+function getBorderLeftWidth() { return getBorderWidth("Left"); }\r
+function getBorderRightWidth() { return getBorderWidth("Right"); }\r
+function getBorderTopWidth() { return getBorderWidth("Top"); }\r
+function getBorderBottomWidth() { return getBorderWidth("Bottom"); }\r
+/* end border width getters */\r
+\r
+/* padding getters */\r
+function getPadding(sSide) {\r
+       var n = parseInt(element.currentStyle["padding" + sSide]);\r
+       return n || 0;\r
+}\r
+\r
+function getPaddingLeft() { return getPadding("Left"); }\r
+function getPaddingRight() { return getPadding("Right"); }\r
+function getPaddingTop() { return getPadding("Top"); }\r
+function getPaddingBottom() { return getPadding("Bottom"); }\r
+/* end padding getters */\r
+\r
+function getBoxSizing() {\r
+       var s = element.style;\r
+       var cs = element.currentStyle\r
+\r
+       if (typeof s.boxSizing != "undefined" && s.boxSizing != "")\r
+               return s.boxSizing;\r
+       if (typeof s["box-sizing"] != "undefined" && s["box-sizing"] != "")\r
+               return s["box-sizing"];\r
+       if (typeof cs.boxSizing != "undefined" && cs.boxSizing != "")\r
+               return cs.boxSizing;\r
+       if (typeof cs["box-sizing"] != "undefined" && cs["box-sizing"] != "")\r
+               return cs["box-sizing"];\r
+       return getDocumentBoxSizing();\r
+}\r
+\r
+function getDocumentBoxSizing() {\r
+       if (doc.compatMode == null || doc.compatMode == "BackCompat")\r
+               return "border-box";\r
+       return "content-box"\r
+}\r
+\r
+/* width and height setters */\r
+function setBorderBoxWidth(n) {\r
+       element.runtimeStyle.width = Math.max(0, n - getBorderLeftWidth() -\r
+               getPaddingLeft() - getPaddingRight() - getBorderRightWidth()) + "px";\r
+}\r
+\r
+function setBorderBoxHeight(n) {\r
+       element.runtimeStyle.height = Math.max(0, n - getBorderTopWidth() -\r
+               getPaddingTop() - getPaddingBottom() - getBorderBottomWidth()) + "px";\r
+}\r
+\r
+function setContentBoxWidth(n) {\r
+       element.runtimeStyle.width = Math.max(0, n + getBorderLeftWidth() +\r
+               getPaddingLeft() + getPaddingRight() + getBorderRightWidth()) + "px";\r
+}\r
+\r
+function setContentBoxHeight(n) {\r
+       element.runtimeStyle.height = Math.max(0, n + getBorderTopWidth() +\r
+               getPaddingTop() + getPaddingBottom() + getBorderBottomWidth()) + "px";\r
+}\r
+/* end width and height setters */\r
+\r
+function updateBorderBoxWidth() {\r
+       element.runtimeStyle.width = "";\r
+       if (getDocumentBoxSizing() == getBoxSizing())\r
+               return;\r
+       var csw = element.currentStyle.width;\r
+       if (csw != "auto" && csw.indexOf("px") != -1) {\r
+               if (getBoxSizing() == "border-box")\r
+                       setBorderBoxWidth(parseInt(csw));\r
+               else\r
+                       setContentBoxWidth(parseInt(csw));\r
+       }\r
+}\r
+\r
+function updateBorderBoxHeight() {\r
+       element.runtimeStyle.height = "";       \r
+       if (getDocumentBoxSizing() == getBoxSizing())\r
+               return;\r
+       var csh = element.currentStyle.height;\r
+       if (csh != "auto" && csh.indexOf("px") != -1) {\r
+               if (getBoxSizing() == "border-box")\r
+                       setBorderBoxHeight(parseInt(csh));\r
+               else\r
+                       setContentBoxHeight(parseInt(csh));\r
+       }\r
+}\r
+\r
+function checkPropertyChange() {\r
+       var pn = event.propertyName;\r
+       var undef;\r
+\r
+       if (pn == "style.boxSizing" && element.style.boxSizing == "") {\r
+               element.style.removeAttribute("boxSizing");\r
+               element.runtimeStyle.boxSizing = undef;         \r
+       }\r
+\r
+\r
+       switch (pn) {\r
+               case "style.width":\r
+               case "style.borderLeftWidth":\r
+               case "style.borderLeftStyle":\r
+               case "style.borderRightWidth":\r
+               case "style.borderRightStyle":\r
+               case "style.paddingLeft":\r
+               case "style.paddingRight":\r
+                       updateBorderBoxWidth();\r
+                       break;\r
+               \r
+               case "style.height":\r
+               case "style.borderTopWidth":\r
+               case "style.borderTopStyle":\r
+               case "style.borderBottomWidth":\r
+               case "style.borderBottomStyle":\r
+               case "style.paddingTop":\r
+               case "style.paddingBottom":\r
+                       updateBorderBoxHeight();\r
+                       break;\r
+               \r
+               case "className":\r
+               case "style.boxSizing":\r
+                       updateBorderBoxWidth();\r
+                       updateBorderBoxHeight();\r
+                       break;\r
+       }\r
+}\r
+\r
+init();\r
+\r
+//]]>\r
+</script>\r
+</component>
\ No newline at end of file
diff --git a/pub/qdb/res/themes/default/js/slider/range.js b/pub/qdb/res/themes/default/js/slider/range.js
new file mode 100644 (file)
index 0000000..54f9a48
--- /dev/null
@@ -0,0 +1,132 @@
+/*----------------------------------------------------------------------------\\r
+|                                Range Class                                  |\r
+|-----------------------------------------------------------------------------|\r
+|                         Created by Erik Arvidsson                           |\r
+|                  (http://webfx.eae.net/contact.html#erik)                   |\r
+|                      For WebFX (http://webfx.eae.net/)                      |\r
+|-----------------------------------------------------------------------------|\r
+| Used to  model the data  used  when working  with  sliders,  scrollbars and |\r
+| progress bars.  Based  on  the  ideas of  the javax.swing.BoundedRangeModel |\r
+| interface  defined  by  Sun  for  Java;   http://java.sun.com/products/jfc/ |\r
+| swingdoc-api-1.0.3/com/sun/java/swing/BoundedRangeModel.html                |\r
+|-----------------------------------------------------------------------------|\r
+|                Copyright (c) 2002, 2005, 2006 Erik Arvidsson                |\r
+|-----------------------------------------------------------------------------|\r
+| Licensed under the Apache License, Version 2.0 (the "License"); you may not |\r
+| use this file except in compliance with the License.  You may obtain a copy |\r
+| of the License at http://www.apache.org/licenses/LICENSE-2.0                |\r
+| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |\r
+| Unless  required  by  applicable law or  agreed  to  in  writing,  software |\r
+| distributed under the License is distributed on an  "AS IS" BASIS,  WITHOUT |\r
+| WARRANTIES OR  CONDITIONS OF ANY KIND,  either express or implied.  See the |\r
+| License  for the  specific language  governing permissions  and limitations |\r
+| under the License.                                                          |\r
+|-----------------------------------------------------------------------------|\r
+| 2002-10-14 | Original version released                                      |\r
+| 2005-10-27 | Use Math.round instead of Math.floor                           |\r
+| 2006-05-28 | Changed license to Apache Software License 2.0.                |\r
+|-----------------------------------------------------------------------------|\r
+| Created 2002-10-14 | All changes are in the log above. | Updated 2006-05-28 |\r
+\----------------------------------------------------------------------------*/\r
+\r
+\r
+function Range() {\r
+       this._value = 0;\r
+       this._minimum = 0;\r
+       this._maximum = 100;\r
+       this._extent = 0;\r
+\r
+       this._isChanging = false;\r
+}\r
+\r
+Range.prototype.setValue = function (value) {\r
+       value = Math.round(parseFloat(value));\r
+       if (isNaN(value)) return;\r
+       if (this._value != value) {\r
+               if (value + this._extent > this._maximum)\r
+                       this._value = this._maximum - this._extent;\r
+               else if (value < this._minimum)\r
+                       this._value = this._minimum;\r
+               else\r
+                       this._value = value;\r
+               if (!this._isChanging && typeof this.onchange == "function")\r
+                        this.onchange();\r
+       }\r
+};\r
+\r
+Range.prototype.getValue = function () {\r
+       return this._value;\r
+};\r
+\r
+Range.prototype.setExtent = function (extent) {\r
+       if (this._extent != extent) {\r
+               if (extent < 0)\r
+                       this._extent = 0;\r
+               else if (this._value + extent > this._maximum)\r
+                       this._extent = this._maximum - this._value;\r
+               else\r
+                       this._extent = extent;\r
+               if (!this._isChanging && typeof this.onchange == "function")\r
+                       this.onchange();\r
+       }\r
+};\r
+\r
+Range.prototype.getExtent = function () {\r
+       return this._extent;\r
+};\r
+\r
+Range.prototype.setMinimum = function (minimum) {\r
+       if (this._minimum != minimum) {\r
+               var oldIsChanging = this._isChanging;\r
+               this._isChanging = true;\r
+\r
+               this._minimum = minimum;\r
+\r
+               if (minimum > this._value)\r
+                       this.setValue(minimum);\r
+               if (minimum > this._maximum) {\r
+                       this._extent = 0;\r
+                       this.setMaximum(minimum);\r
+                       this.setValue(minimum)\r
+               }\r
+               if (minimum + this._extent > this._maximum)\r
+                       this._extent = this._maximum - this._minimum;\r
+\r
+               this._isChanging = oldIsChanging;\r
+               if (!this._isChanging && typeof this.onchange == "function")\r
+                       this.onchange();\r
+       }\r
+};\r
+\r
+Range.prototype.getMinimum = function () {\r
+       return this._minimum;\r
+};\r
+\r
+Range.prototype.setMaximum = function (maximum) {\r
+       if (this._maximum != maximum) {\r
+               var oldIsChanging = this._isChanging;\r
+               this._isChanging = true;\r
+\r
+               this._maximum = maximum;\r
+\r
+               if (maximum < this._value)\r
+                       this.setValue(maximum - this._extent);\r
+               if (maximum < this._minimum) {\r
+                       this._extent = 0;\r
+                       this.setMinimum(maximum);\r
+                       this.setValue(this._maximum);\r
+               }\r
+               if (maximum < this._minimum + this._extent)\r
+                       this._extent = this._maximum - this._minimum;\r
+               if (maximum < this._value + this._extent)\r
+                       this._extent = this._maximum - this._value;\r
+\r
+               this._isChanging = oldIsChanging;\r
+               if (!this._isChanging && typeof this.onchange == "function")\r
+                       this.onchange();\r
+       }\r
+};\r
+\r
+Range.prototype.getMaximum = function () {\r
+       return this._maximum;\r
+};\r
diff --git a/pub/qdb/res/themes/default/js/slider/slider.js b/pub/qdb/res/themes/default/js/slider/slider.js
new file mode 100644 (file)
index 0000000..865a625
--- /dev/null
@@ -0,0 +1,489 @@
+/*----------------------------------------------------------------------------\\r
+|                                Slider 1.02                                  |\r
+|-----------------------------------------------------------------------------|\r
+|                         Created by Erik Arvidsson                           |\r
+|                  (http://webfx.eae.net/contact.html#erik)                   |\r
+|                      For WebFX (http://webfx.eae.net/)                      |\r
+|-----------------------------------------------------------------------------|\r
+| A  slider  control that  degrades  to an  input control  for non  supported |\r
+| browsers.                                                                   |\r
+|-----------------------------------------------------------------------------|\r
+|                Copyright (c) 2002, 2003, 2006 Erik Arvidsson                |\r
+|-----------------------------------------------------------------------------|\r
+| Licensed under the Apache License, Version 2.0 (the "License"); you may not |\r
+| use this file except in compliance with the License.  You may obtain a copy |\r
+| of the License at http://www.apache.org/licenses/LICENSE-2.0                |\r
+| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |\r
+| Unless  required  by  applicable law or  agreed  to  in  writing,  software |\r
+| distributed under the License is distributed on an  "AS IS" BASIS,  WITHOUT |\r
+| WARRANTIES OR  CONDITIONS OF ANY KIND,  either express or implied.  See the |\r
+| License  for the  specific language  governing permissions  and limitations |\r
+| under the License.                                                          |\r
+|-----------------------------------------------------------------------------|\r
+| Dependencies: timer.js - an OO abstraction of timers                        |\r
+|               range.js - provides the data model for the slider             |\r
+|               winclassic.css or any other css file describing the look      |\r
+|-----------------------------------------------------------------------------|\r
+| 2002-10-14 | Original version released                                      |\r
+| 2003-03-27 | Added a test in the constructor for missing oElement arg       |\r
+| 2003-11-27 | Only use mousewheel when focused                               |\r
+| 2006-05-28 | Changed license to Apache Software License 2.0.                |\r
+|-----------------------------------------------------------------------------|\r
+| Created 2002-10-14 | All changes are in the log above. | Updated 2006-05-28 |\r
+\----------------------------------------------------------------------------*/\r
+\r
+Slider.isSupported = typeof document.createElement != "undefined" &&\r
+       typeof document.documentElement != "undefined" &&\r
+       typeof document.documentElement.offsetWidth == "number";\r
+\r
+\r
+function Slider(oElement, oInput, sOrientation) {\r
+       if (!oElement) return;\r
+       this._orientation = sOrientation || "horizontal";\r
+       this._range = new Range();\r
+       this._range.setExtent(0);\r
+       this._blockIncrement = 10;\r
+       this._unitIncrement = 1;\r
+       this._timer = new Timer(100);\r
+\r
+\r
+       if (Slider.isSupported && oElement) {\r
+\r
+               this.document = oElement.ownerDocument || oElement.document;\r
+\r
+               this.element = oElement;\r
+               this.element.slider = this;\r
+               this.element.unselectable = "on";\r
+\r
+               // add class name tag to class name\r
+               this.element.className = this._orientation + " " + this.classNameTag + " " + this.element.className;\r
+\r
+               // create line\r
+               this.line = this.document.createElement("DIV");\r
+               this.line.className = "line";\r
+               this.line.unselectable = "on";\r
+               this.line.appendChild(this.document.createElement("DIV"));\r
+               this.element.appendChild(this.line);\r
+\r
+               // create handle\r
+               this.handle = this.document.createElement("DIV");\r
+               this.handle.className = "handle";\r
+               this.handle.unselectable = "on";\r
+               this.handle.appendChild(this.document.createElement("DIV"));\r
+               this.handle.firstChild.appendChild(\r
+                       this.document.createTextNode(String.fromCharCode(160)));\r
+               this.element.appendChild(this.handle);\r
+       }\r
+\r
+       this.input = oInput;\r
+\r
+       // events\r
+       var oThis = this;\r
+       this._range.onchange = function () {\r
+               oThis.recalculate();\r
+               if (typeof oThis.onchange == "function")\r
+                       oThis.onchange();\r
+       };\r
+\r
+       if (Slider.isSupported && oElement) {\r
+               this.element.onfocus            = Slider.eventHandlers.onfocus;\r
+               this.element.onblur                     = Slider.eventHandlers.onblur;\r
+               this.element.onmousedown        = Slider.eventHandlers.onmousedown;\r
+               this.element.onmouseover        = Slider.eventHandlers.onmouseover;\r
+               this.element.onmouseout         = Slider.eventHandlers.onmouseout;\r
+               this.element.onkeydown          = Slider.eventHandlers.onkeydown;\r
+               this.element.onkeypress         = Slider.eventHandlers.onkeypress;\r
+               this.element.onmousewheel       = Slider.eventHandlers.onmousewheel;\r
+               this.handle.onselectstart       =\r
+               this.element.onselectstart      = function () { return false; };\r
+\r
+               this._timer.ontimer = function () {\r
+                       oThis.ontimer();\r
+               };\r
+\r
+               // extra recalculate for ie\r
+               window.setTimeout(function() {\r
+                       oThis.recalculate();\r
+               }, 1);\r
+       }\r
+       else {\r
+               this.input.onchange = function (e) {\r
+                       oThis.setValue(oThis.input.value);\r
+               };\r
+       }\r
+}\r
+\r
+Slider.eventHandlers = {\r
+\r
+       // helpers to make events a bit easier\r
+       getEvent:       function (e, el) {\r
+               if (!e) {\r
+                       if (el)\r
+                               e = el.document.parentWindow.event;\r
+                       else\r
+                               e = window.event;\r
+               }\r
+               if (!e.srcElement) {\r
+                       var el = e.target;\r
+                       while (el != null && el.nodeType != 1)\r
+                               el = el.parentNode;\r
+                       e.srcElement = el;\r
+               }\r
+               if (typeof e.offsetX == "undefined") {\r
+                       e.offsetX = e.layerX;\r
+                       e.offsetY = e.layerY;\r
+               }\r
+\r
+               return e;\r
+       },\r
+\r
+       getDocument:    function (e) {\r
+               if (e.target)\r
+                       return e.target.ownerDocument;\r
+               return e.srcElement.document;\r
+       },\r
+\r
+       getSlider:      function (e) {\r
+               var el = e.target || e.srcElement;\r
+               while (el != null && el.slider == null) {\r
+                       el = el.parentNode;\r
+               }\r
+               if (el)\r
+                       return el.slider;\r
+               return null;\r
+       },\r
+\r
+       getLine:        function (e) {\r
+               var el = e.target || e.srcElement;\r
+               while (el != null && el.className != "line")    {\r
+                       el = el.parentNode;\r
+               }\r
+               return el;\r
+       },\r
+\r
+       getHandle:      function (e) {\r
+               var el = e.target || e.srcElement;\r
+               var re = /handle/;\r
+               while (el != null && !re.test(el.className))    {\r
+                       el = el.parentNode;\r
+               }\r
+               return el;\r
+       },\r
+       // end helpers\r
+\r
+       onfocus:        function (e) {\r
+               var s = this.slider;\r
+               s._focused = true;\r
+               s.handle.className = "handle hover";\r
+       },\r
+\r
+       onblur: function (e) {\r
+               var s = this.slider\r
+               s._focused = false;\r
+               s.handle.className = "handle";\r
+       },\r
+\r
+       onmouseover:    function (e) {\r
+               e = Slider.eventHandlers.getEvent(e, this);\r
+               var s = this.slider;\r
+               if (e.srcElement == s.handle)\r
+                       s.handle.className = "handle hover";\r
+       },\r
+\r
+       onmouseout:     function (e) {\r
+               e = Slider.eventHandlers.getEvent(e, this);\r
+               var s = this.slider;\r
+               if (e.srcElement == s.handle && !s._focused)\r
+                       s.handle.className = "handle";\r
+       },\r
+\r
+       onmousedown:    function (e) {\r
+               e = Slider.eventHandlers.getEvent(e, this);\r
+               var s = this.slider;\r
+               if (s.element.focus)\r
+                       s.element.focus();\r
+\r
+     &