Browse Source

Initial import

master
Chris Smith 14 years ago
commit
41c1851668
40 changed files with 9939 additions and 0 deletions
  1. 5
    0
      .gitignore
  2. 20
    0
      cron.inc.php
  3. 17
    0
      database.inc.php
  4. BIN
      images/bullet.gif
  5. BIN
      images/close.gif
  6. BIN
      images/closelabel.gif
  7. BIN
      images/donate-button.gif
  8. BIN
      images/download-icon.gif
  9. BIN
      images/image-1.jpg
  10. BIN
      images/loading.gif
  11. BIN
      images/nextlabel.gif
  12. BIN
      images/prevlabel.gif
  13. BIN
      images/thumb-1.jpg
  14. 31
    0
      index.php
  15. 29
    0
      info.php
  16. 34
    0
      logo.cron.php
  17. 182
    0
      logo.inc.php
  18. 44
    0
      logo.matrix.inc.php
  19. 49
    0
      logo.php
  20. 197
    0
      logodetect.php
  21. 17
    0
      logotest.php
  22. 22
    0
      ocr.cron.php
  23. 22
    0
      ocr.php
  24. 82
    0
      prog.php
  25. 136
    0
      res/builder.js
  26. 965
    0
      res/controls.js
  27. 52
    0
      res/dms.js
  28. 974
    0
      res/dragdrop.js
  29. 1122
    0
      res/effects.js
  30. 28
    0
      res/lightbox.css
  31. 497
    0
      res/lightbox.js
  32. 4221
    0
      res/prototype.js
  33. 58
    0
      res/scriptaculous.js
  34. 275
    0
      res/slider.js
  35. 55
    0
      res/sound.js
  36. 103
    0
      res/style.css
  37. BIN
      res/throbber.gif
  38. 568
    0
      res/unittest.js
  39. 20
    0
      search.php
  40. 114
    0
      thumb.php

+ 5
- 0
.gitignore View File

@@ -0,0 +1,5 @@
1
+/docs/*
2
+/.ht*
3
+/.logos/*
4
+/.thumbs/*
5
+/settings.php

+ 20
- 0
cron.inc.php View File

@@ -0,0 +1,20 @@
1
+<?PHP
2
+
3
+function checkDir($dir, $prefix) {
4
+ $files = glob($dir . '/*');
5
+ $results = array();
6
+
7
+ foreach ($files as $file) {
8
+  $filename = (empty($prefix) ? '' : $prefix . '/') . basename($file);
9
+
10
+  if (is_dir($file)) {
11
+   $results = array_merge($results, checkDir($file, $filename));
12
+  } else {
13
+   $results[] = $filename;
14
+  }
15
+ }
16
+
17
+ return $results;
18
+}
19
+
20
+?>

+ 17
- 0
database.inc.php View File

@@ -0,0 +1,17 @@
1
+<?PHP
2
+
3
+if (file_exists('settings.php')) {
4
+ require('settings.php');
5
+} else {
6
+ define('DB_HOST', 'localhost');
7
+ define('DB_USER', 'username');
8
+ define('DB_PASS', 'password');
9
+ define('DB_NAME', 'db_name');
10
+}
11
+
12
+mysql_connect(DB_HOST, DB_USER, DB_PASS);
13
+mysql_select_db(DB_NAME);
14
+
15
+function s($sql) { return mysql_real_escape_string($sql); }
16
+
17
+?>

BIN
images/bullet.gif View File


BIN
images/close.gif View File


BIN
images/closelabel.gif View File


BIN
images/donate-button.gif View File


BIN
images/download-icon.gif View File


BIN
images/image-1.jpg View File


BIN
images/loading.gif View File


BIN
images/nextlabel.gif View File


BIN
images/prevlabel.gif View File


BIN
images/thumb-1.jpg View File


+ 31
- 0
index.php View File

@@ -0,0 +1,31 @@
1
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2
+<html>
3
+ <head>
4
+  <title>Document Management</title>
5
+  <script src="res/prototype.js" type="text/javascript"></script>
6
+  <script src="res/scriptaculous.js" type="text/javascript"></script>
7
+  <script src="res/lightbox.js" type="text/javascript"></script>
8
+  <script src="res/dms.js" type="text/javascript"></script>
9
+  <link rel="stylesheet" href="res/style.css" type="text/css">
10
+  <link rel="stylesheet" href="res/lightbox.css" type="text/css">
11
+ </head>
12
+ <body>
13
+  <div id="search">
14
+   Search: <input type="text" id="search_text" onkeypress="doSearch();">
15
+   <img src="res/throbber.gif" id="search_throbber" style="visibility: hidden; vertical-align: middle;">
16
+  </div>
17
+  <div id="searchresults">
18
+   <?PHP include('search.php'); ?>
19
+  </div>
20
+  <ul id="previewtabs">
21
+   <li><a href="#" id="preview_preview" class="active" onclick="showTab('preview');">Preview</a></li>
22
+   <li><a href="#" id="preview_info" onclick="showTab('info');">Image info</a></li>
23
+   <li><a href="#" id="preview_ocr" onclick="showTab('ocr');">OCR results</a></li>
24
+   <li><a href="#" id="preview_logo" onclick="showTab('logo');">Logo detect</a></li>
25
+  </ul>
26
+  <div class="preview" id="info"></div>
27
+  <div class="preview" id="ocr"></div>
28
+  <div class="preview" id="logo"></div>
29
+  <div class="preview" id="preview"><a id="previewlink" href="" rel="lightbox"><img id="previewimg"></a></div>
30
+ </body>
31
+</html>

+ 29
- 0
info.php View File

@@ -0,0 +1,29 @@
1
+<?PHP
2
+
3
+ $image = $_POST['image'];
4
+ 
5
+ if (!preg_match('/^([a-z]+\/)+[a-z]+[-_][0-9]+\.jpe?g$/', $image)) {
6
+  die('Error: ' . htmlentities($image));
7
+ }
8
+
9
+?>
10
+<h2>Image information</h2>
11
+<table>
12
+ <tr>
13
+  <th>Path</th>
14
+  <td><?PHP echo htmlentities($image); ?></td>
15
+ </tr>
16
+ <tr>
17
+  <th>Creation time</th>
18
+  <td><?PHP echo date('r', filemtime($image)); ?></td>
19
+ </tr>
20
+ <tr>
21
+  <th>File size</th>
22
+  <td><?PHP echo number_format(filesize($image)); ?> B</td>
23
+ </tr>
24
+</table>
25
+<h2>Related documents</h2>
26
+<h3>Documents scanned at same time</h3>
27
+<p>(Not implemented yet)</p>
28
+<h3>Other pages in this document</h3>
29
+<p>(Not implemented yet)</p>

+ 34
- 0
logo.cron.php View File

@@ -0,0 +1,34 @@
1
+<?PHP
2
+
3
+ set_time_limit(0);
4
+
5
+ require_once('database.inc.php');
6
+ require_once('cron.inc.php');
7
+ require_once('logo.inc.php');
8
+ require_once('logo.matrix.inc.php');
9
+
10
+foreach (checkDir('docs', '') as $doc) {
11
+ $sql = 'SELECT COUNT(*) FROM logoresults WHERE logo_image = \'' . s($doc) . '\'';
12
+ $res = mysql_query($sql);
13
+ $num = mysql_result($res, 0);
14
+
15
+ if ($num == 0) {
16
+  echo "Determining logo and matrix for $doc ... ";
17
+  $image  = imagecreatefromjpeg('docs/' . $doc);
18
+  $logo   = getLogo($image);
19
+  $matrix = getLogoMatrix($logo);  
20
+
21
+  ob_start();
22
+  imagejpeg($logo);
23
+  $logo = ob_get_contents();
24
+  ob_end_clean();
25
+
26
+  $sql  = 'INSERT INTO logoresults (logo_image, logo_logo, logo_matrix) VALUES (\'';
27
+  $sql .= s($doc) . '\', \'' . s($logo) . '\', \'' . $matrix . '\')';
28
+  mysql_query($sql);
29
+
30
+  echo "Done\n";
31
+ }
32
+}
33
+
34
+?>

+ 182
- 0
logo.inc.php View File

@@ -0,0 +1,182 @@
1
+<?PHP
2
+
3
+ function isClear(&$im, $x, $y) {
4
+  $rgb = imagecolorat($im, $x, $y);
5
+  $r = ($rgb >> 16) & 0xFF;
6
+  $g = ($rgb >> 8) & 0xFF;
7
+  $b = $rgb & 0xFF;
8
+
9
+  return $r + $g + $b > 600;
10
+ }
11
+
12
+ function getLineScore(&$im, $y, $xmin, $xmax) {
13
+  $count = 0;
14
+
15
+  for ($x = $xmin; $x < $xmax; $x++) {
16
+   if (isClear($im, $x, $y)) {
17
+    $count++; 
18
+   }
19
+  }
20
+
21
+  return $count / ($xmax - $xmin);
22
+ }
23
+
24
+ function getColScore(&$im, $ymin, $ymax, $x) {
25
+  $count = 0;
26
+
27
+  for ($y = $ymin; $y < $ymax; $y++) {
28
+   if (isClear($im, $x, $y)) {
29
+    $count++;
30
+   }
31
+  }
32
+
33
+  return $count / ($ymax - $ymin);
34
+ }
35
+
36
+ // Number of blank lines required in a row
37
+ define('BLANK_THRESHOLD', 15);
38
+
39
+ function doBlankCols(&$im, $ymin, $ymax, $xmin, $xmax, $colour = false) {
40
+  // Check for blank columns
41
+
42
+  if ($xmin == $xmax || $ymin == $ymax) { return array(); }
43
+
44
+  $lastx = $laststreak = -100; $count = 0; $res = array();
45
+
46
+  for ($x = $xmin; $x <= $xmax; $x++) {
47
+   $score = getColScore($im, $ymin, $ymax, $x);
48
+
49
+   if ($score > 0.99) {
50
+    if (++$lastx == $x) {
51
+     $count++;
52
+
53
+     if ($count == BLANK_THRESHOLD) {
54
+      if ($colour) { imagefilledrectangle($im, $x - $count, $ymin, $x, $ymax, imagecolorallocatealpha($im, 0, 0, 0, 50));  }
55
+
56
+      $res = array_merge($res, doBlankLines($im, $ymin, $ymax, max($xmin, $laststreak + 1), $x - $count - 1, $colour));
57
+
58
+      $laststreak = $x;
59
+     } else if ($count > BLANK_THRESHOLD) {
60
+      if ($colour) { imageline($im, $x, $ymin, $x, $ymax, imagecolorallocatealpha($im, 0, 0, 0, 50)); }
61
+      $laststreak = $x;
62
+     }
63
+    } else {
64
+     $lastx = $x;
65
+     $count = 1;
66
+    }
67
+   }
68
+  }
69
+
70
+  if (count($res) > 0 && $laststreak + 1 < $xmax) {
71
+   $res = array_merge($res, doBlankLines($im, $ymin, $ymax, max($xmin, $laststreak + 1), $xmax));
72
+  }
73
+
74
+  if (count($res) == 0) {
75
+   $res[] = array($ymin, $ymax, $xmin, $xmax);
76
+  }
77
+
78
+  return $res;
79
+ }
80
+
81
+ function doBlankLines(&$im, $ymin, $ymax, $xmin, $xmax, $colour = false) {
82
+  // Check for blank lines
83
+
84
+  if ($xmin == $xmax || $ymin == $ymax) { return array(); }
85
+
86
+  $lasty = $laststreak = -100; $count = 0; $res = array();
87
+
88
+  for ($y = $ymin; $y <= $ymax; $y++) {
89
+   $score = getLineScore($im, $y, $xmin, $xmax);
90
+
91
+   if ($xmin > 0) {
92
+    //imageline($im, $xmin, $y, $xmax, $y, imagecolorallocatealpha($im, 0, 0xFF * $score, 0, 50));
93
+   }
94
+
95
+   if ($score > 0.99) {
96
+    if (++$lasty == $y) {
97
+     $count++;
98
+ 
99
+     if ($count == BLANK_THRESHOLD) {
100
+      if ($colour) {
101
+       imagefilledrectangle($im, $xmin, $y - $count, $xmax, $y, imagecolorallocatealpha($im, 0, 0xFF, 0, 50));
102
+      }
103
+
104
+      $res = array_merge($res, doBlankCols($im, max($ymin, 1 + $laststreak), $y - $count, $xmin, $xmax, $colour));
105
+     
106
+      $laststreak = $y;
107
+     } else if ($count > BLANK_THRESHOLD) {
108
+      if ($colour) { imageline($im, $xmin, $y, $xmax, $y, imagecolorallocatealpha($im, 0, 0xFF, 0, 50)); }
109
+
110
+      $laststreak = $y;
111
+     }
112
+    } else {
113
+     $count = 1;
114
+     $lasty = $y;
115
+    }
116
+   }
117
+  }
118
+
119
+  if (count($res) > 0 && $laststreak + 1 < $ymax) {
120
+   $res = array_merge($res, doBlankCols($im, max($ymin, 1 + $laststreak), $ymax, $xmin, $xmax));
121
+  }
122
+
123
+  if (count($res) == 0) {
124
+   $res[] = array($ymin, $ymax, $xmin, $xmax);
125
+  }
126
+
127
+  return $res;
128
+ }
129
+
130
+ function logoFilter1($logo) {
131
+  global $im;
132
+
133
+  $height = $logo[1] - $logo[0];
134
+  $width = $logo[3] - $logo[2];
135
+  
136
+  if ($width < 3 * BLANK_THRESHOLD || $height < 3 * BLANK_THRESHOLD) { return false; }
137
+  if ($width > 0.4 * imagesx($im)) { return false; }
138
+
139
+  return true;
140
+ }
141
+
142
+ function logoFilter2($logo) {
143
+  global $im, $logos;
144
+
145
+  $left = $logo[2] < 0.5 * imagesx($im);
146
+
147
+  foreach ($logos as $other) {
148
+   if (($left && $other[2] < $logo[2] - 20) || (!$left && $other[3] > $logo[3] + 20)) {
149
+    return false;
150
+   }
151
+  }
152
+
153
+  return true;
154
+ }
155
+
156
+ function logoFilter3($logo) {
157
+  global $logos;
158
+
159
+  foreach ($logos as $other) {
160
+   if ($other[0] < $logo[0]) { return false; }
161
+  }
162
+
163
+  return true;
164
+ }
165
+
166
+ function getLogo($im) {
167
+  $logos = doBlankLines($im, 0, imagesy($im) / 3, 0, imagesx($im), false);
168
+
169
+  $GLOBALS['im'] =& $im;
170
+  $GLOBALS['logos'] =& $logos;
171
+
172
+  $logos = array_filter($logos, 'logoFilter1');
173
+  $logos = array_filter($logos, 'logoFilter2');
174
+  $logos = array_filter($logos, 'logoFilter3');
175
+
176
+  $logo = array_pop($logos);
177
+  $im2 = imagecreatetruecolor($logo[3] - $logo[2], $logo[1] - $logo[0]);
178
+  imagecopy($im2, $im, 0, 0, $logo[2], $logo[0], $logo[3] - $logo[2], $logo[1] - $logo[0]);
179
+
180
+  return $im2;
181
+ }
182
+?>

+ 44
- 0
logo.matrix.inc.php View File

@@ -0,0 +1,44 @@
1
+<?PHP
2
+
3
+ function getLogoMatrix($logo) {
4
+  $matrix = array();
5
+
6
+  for ($x = 0; $x < imagesx($logo); $x++) {
7
+   for ($y = 0; $y < imagesy($logo); $y++) {
8
+    $c = imagecolorat($logo, $x, $y);
9
+    $r = 255 * round((($c >> 16) & 0xFF) / 255, 0);
10
+    $g = 255 * round((($c >>  8) & 0xFF) / 255, 0);
11
+    $b = 255 * round(( $c        & 0xFF) / 255, 0);
12
+
13
+    $xo = floor(10 * $x / imagesx($logo));
14
+    $yo = floor( 5 * $y / imagesy($logo));
15
+
16
+    if (!isset($matrix[$xo][$yo])) {
17
+     $matrix[$xo][$yo] = array('r' => 0, 'g' => 0, 'b' => 0, 'c' => 0);
18
+    }
19
+
20
+    $matrix[$xo][$yo]['r'] += $r;
21
+    $matrix[$xo][$yo]['g'] += $g;
22
+    $matrix[$xo][$yo]['b'] += $b;
23
+    $matrix[$xo][$yo]['c']++;
24
+   }
25
+  }
26
+
27
+  $res = '';
28
+
29
+  foreach ($matrix as $x => $row) {
30
+   foreach ($row as $y => $data) {
31
+    extract($data); 
32
+
33
+    $r /= $c; $g /= $c; $b /= $c;
34
+    $r = floor($r); $g = floor($g); $b = floor($b);
35
+    $r = round($r / 127); $g = round($g / 127); $b = round($b / 127);
36
+    $v = chr(ord('A') + $r + $g * 3 + $b * 6);
37
+    $res .= $v; 
38
+   }
39
+  }
40
+
41
+  return $res;
42
+ }
43
+
44
+?>

+ 49
- 0
logo.php View File

@@ -0,0 +1,49 @@
1
+<?PHP
2
+
3
+ $image = $_POST['image'];
4
+ 
5
+ if (!preg_match('/^([a-z]+\/)+[a-z]+[-_][0-9]+\.jpe?g$/', $image)) {
6
+  die('Error: ' . htmlentities($image));
7
+ }
8
+
9
+?>
10
+<h2>Logo detect</h2>
11
+<h3>Detected logo</h3>
12
+<?PHP
13
+
14
+ if (file_exists('.logos/' . md5($image))) {
15
+  echo '<img src=".logos/', md5($image), '">';
16
+ }
17
+
18
+?>
19
+<h3>Documents with similar logos</h3>
20
+<?PHP
21
+ require_once('database.inc.php');
22
+
23
+ $sql = 'SELECT logo_image, logo_matrix FROM logoresults WHERE LENGTH(logo_matrix) > 0';
24
+ $res = mysql_query($sql);
25
+ $data = array();
26
+
27
+ while ($row = mysql_fetch_assoc($res)) {
28
+  $data[$row['logo_image']] = $row['logo_matrix'];
29
+ }
30
+
31
+ $timage = substr($image, 5);
32
+
33
+ if (isset($data[$timage])) {
34
+  $target = $data[$timage];
35
+  unset($data[$timage]);
36
+
37
+  foreach ($data as $key => $value) {
38
+   $data[$key] = levenshtein($value, $target);
39
+  }
40
+
41
+  asort($data);
42
+  foreach ($data as $key => $value) {
43
+   if ($value < 10) {
44
+    echo '<div style="float: left; text-align: center; font-size: small;"><img src="thumb.php?', 'docs/', $key, '"><br>(', $value, ')</div>';
45
+   }
46
+  }
47
+ }
48
+
49
+?>

+ 197
- 0
logodetect.php View File

@@ -0,0 +1,197 @@
1
+<?PHP
2
+
3
+ set_time_limit(120);
4
+
5
+ if (!defined('logodetect')) {
6
+ function isClear(&$im, $x, $y) {
7
+  $rgb = imagecolorat($im, $x, $y);
8
+  $r = ($rgb >> 16) & 0xFF;
9
+  $g = ($rgb >> 8) & 0xFF;
10
+  $b = $rgb & 0xFF;
11
+
12
+  return $r + $g + $b > 600;
13
+ }
14
+
15
+ function getLineScore(&$im, $y, $xmin, $xmax) {
16
+  $count = 0;
17
+
18
+  for ($x = $xmin; $x < $xmax; $x++) {
19
+   if (isClear($im, $x, $y)) {
20
+    $count++; 
21
+   }
22
+  }
23
+
24
+  return $count / ($xmax - $xmin);
25
+ }
26
+
27
+ function getColScore(&$im, $ymin, $ymax, $x) {
28
+  $count = 0;
29
+
30
+  for ($y = $ymin; $y < $ymax; $y++) {
31
+   if (isClear($im, $x, $y)) {
32
+    $count++;
33
+   }
34
+  }
35
+
36
+  return $count / ($ymax - $ymin);
37
+ }
38
+ }
39
+
40
+ if (file_exists('.logos/' . md5($_SERVER['QUERY_STRING']))) {
41
+  header('Content-type: image/jpeg');
42
+  readfile('.logos/' . md5($_SERVER['QUERY_STRING']));
43
+  exit;
44
+ }
45
+
46
+ $im = imagecreatefromjpeg($_SERVER['QUERY_STRING']);
47
+
48
+ if (!defined('logodetect')) {
49
+ // Number of blank lines required in a row
50
+ define('BLANK_THRESHOLD', 15);
51
+
52
+ function doBlankCols(&$im, $ymin, $ymax, $xmin, $xmax, $colour = false) {
53
+  // Check for blank columns
54
+
55
+  if ($xmin == $xmax || $ymin == $ymax) { return array(); }
56
+
57
+  $lastx = $laststreak = -100; $count = 0; $res = array();
58
+
59
+  for ($x = $xmin; $x <= $xmax; $x++) {
60
+   $score = getColScore($im, $ymin, $ymax, $x);
61
+
62
+   if ($score > 0.99) {
63
+    if (++$lastx == $x) {
64
+     $count++;
65
+
66
+     if ($count == BLANK_THRESHOLD) {
67
+      if ($colour) { imagefilledrectangle($im, $x - $count, $ymin, $x, $ymax, imagecolorallocatealpha($im, 0, 0, 0, 50));  }
68
+
69
+      $res = array_merge($res, doBlankLines($im, $ymin, $ymax, max($xmin, $laststreak + 1), $x - $count - 1, $colour));
70
+
71
+      $laststreak = $x;
72
+     } else if ($count > BLANK_THRESHOLD) {
73
+      if ($colour) { imageline($im, $x, $ymin, $x, $ymax, imagecolorallocatealpha($im, 0, 0, 0, 50)); }
74
+      $laststreak = $x;
75
+     }
76
+    } else {
77
+     $lastx = $x;
78
+     $count = 1;
79
+    }
80
+   }
81
+  }
82
+
83
+  if (count($res) > 0 && $laststreak + 1 < $xmax) {
84
+   $res = array_merge($res, doBlankLines($im, $ymin, $ymax, max($xmin, $laststreak + 1), $xmax));
85
+  }
86
+
87
+  if (count($res) == 0) {
88
+   $res[] = array($ymin, $ymax, $xmin, $xmax);
89
+  }
90
+
91
+  return $res;
92
+ }
93
+
94
+ function doBlankLines(&$im, $ymin, $ymax, $xmin, $xmax, $colour = false) {
95
+  // Check for blank lines
96
+
97
+  if ($xmin == $xmax || $ymin == $ymax) { return array(); }
98
+
99
+  $lasty = $laststreak = -100; $count = 0; $res = array();
100
+
101
+  for ($y = $ymin; $y <= $ymax; $y++) {
102
+   $score = getLineScore($im, $y, $xmin, $xmax);
103
+
104
+   if ($xmin > 0) {
105
+    //imageline($im, $xmin, $y, $xmax, $y, imagecolorallocatealpha($im, 0, 0xFF * $score, 0, 50));
106
+   }
107
+
108
+   if ($score > 0.99) {
109
+    if (++$lasty == $y) {
110
+     $count++;
111
+ 
112
+     if ($count == BLANK_THRESHOLD) {
113
+      if ($colour) {
114
+       imagefilledrectangle($im, $xmin, $y - $count, $xmax, $y, imagecolorallocatealpha($im, 0, 0xFF, 0, 50));
115
+      }
116
+
117
+      $res = array_merge($res, doBlankCols($im, max($ymin, 1 + $laststreak), $y - $count, $xmin, $xmax, $colour));
118
+     
119
+      $laststreak = $y;
120
+     } else if ($count > BLANK_THRESHOLD) {
121
+      if ($colour) { imageline($im, $xmin, $y, $xmax, $y, imagecolorallocatealpha($im, 0, 0xFF, 0, 50)); }
122
+
123
+      $laststreak = $y;
124
+     }
125
+    } else {
126
+     $count = 1;
127
+     $lasty = $y;
128
+    }
129
+   }
130
+  }
131
+
132
+  if (count($res) > 0 && $laststreak + 1 < $ymax) {
133
+   $res = array_merge($res, doBlankCols($im, max($ymin, 1 + $laststreak), $ymax, $xmin, $xmax));
134
+  }
135
+
136
+  if (count($res) == 0) {
137
+   $res[] = array($ymin, $ymax, $xmin, $xmax);
138
+  }
139
+
140
+  return $res;
141
+ }
142
+
143
+ function logoFilter1($logo) {
144
+  global $im;
145
+
146
+  $height = $logo[1] - $logo[0];
147
+  $width = $logo[3] - $logo[2];
148
+  
149
+  if ($width < 3 * BLANK_THRESHOLD || $height < 3 * BLANK_THRESHOLD) { return false; }
150
+  if ($width > 0.4 * imagesx($im)) { return false; }
151
+
152
+  return true;
153
+ }
154
+
155
+ function logoFilter2($logo) {
156
+  global $im, $logos;
157
+
158
+  $left = $logo[2] < 0.5 * imagesx($im);
159
+
160
+  foreach ($logos as $other) {
161
+   if (($left && $other[2] < $logo[2] - 20) || (!$left && $other[3] > $logo[3] + 20)) {
162
+    return false;
163
+   }
164
+  }
165
+
166
+  return true;
167
+ }
168
+
169
+ function logoFilter3($logo) {
170
+  global $logos;
171
+
172
+  foreach ($logos as $other) {
173
+   if ($other[0] < $logo[0]) { return false; }
174
+  }
175
+
176
+  return true;
177
+ }
178
+ } 
179
+
180
+ $logos = doBlankLines($im, 0, imagesy($im) / 3, 0, imagesx($im), false);
181
+ $logos = array_filter($logos, 'logoFilter1');
182
+ $logos = array_filter($logos, 'logoFilter2');
183
+ $logos = array_filter($logos, 'logoFilter3');
184
+
185
+ $logo = array_pop($logos);
186
+ $im2 = imagecreatetruecolor($logo[3] - $logo[2], $logo[1] - $logo[0]);
187
+ imagecopy($im2, $im, 0, 0, $logo[2], $logo[0], $logo[3] - $logo[2], $logo[1] - $logo[0]);
188
+
189
+ imagejpeg($im2, '.logos/' . md5($_SERVER['QUERY_STRING']));
190
+
191
+ if (!defined('nooutput')) {
192
+  header('Content-type: image/jpeg');
193
+  imagejpeg($im2);
194
+ }
195
+
196
+ define('logodetect', true);
197
+?>

+ 17
- 0
logotest.php View File

@@ -0,0 +1,17 @@
1
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2
+<html>
3
+ <head>
4
+  <title>Document Management</title>
5
+ </head>
6
+ <body>
7
+  <div id="searchresults">
8
+<?PHP
9
+
10
+ foreach (glob('.logos/*') as $logo) {
11
+  echo '<img src="', $logo, '" style="float: left;">';
12
+ }
13
+
14
+?>
15
+  </div>
16
+ </body>
17
+</html>

+ 22
- 0
ocr.cron.php View File

@@ -0,0 +1,22 @@
1
+<?PHP
2
+
3
+require_once('database.inc.php');
4
+require_once('cron.inc.php');
5
+
6
+foreach (checkDir('docs', '') as $doc) {
7
+ $sql = 'SELECT COUNT(*) FROM ocrresults WHERE ocr_image = \'' . $doc . '\'';
8
+ $res = mysql_query($sql);
9
+ $num = mysql_result($res, 0);
10
+
11
+ if ($num == 0) {
12
+  echo "OCRing $doc...\n";
13
+  $gocr = `djpeg -pnm 'docs/$doc' | gocr -`;
14
+  $ocrd = `djpeg -pnm 'docs/$doc' | ocrad`;
15
+
16
+  $sql  = 'INSERT INTO ocrresults (ocr_image, ocr_gocr, ocr_ocrad) VALUES (\'' . $doc . '\', \'';
17
+  $sql .= mysql_real_escape_string($gocr) . '\', \'' . mysql_real_escape_string($ocrd) . '\')';
18
+  mysql_query($sql);
19
+ } 
20
+}
21
+
22
+?>

+ 22
- 0
ocr.php View File

@@ -0,0 +1,22 @@
1
+<?PHP
2
+
3
+ require_once('database.inc.php');
4
+
5
+ $image = $_POST['image'];
6
+ 
7
+ if (!preg_match('/^([a-z]+\/)+[a-z]+[-_][0-9]+\.jpe?g$/', $image) || !file_exists($image)) {
8
+  die('Error: ' . htmlentities($image));
9
+ }
10
+
11
+ $db = substr($image, 5);
12
+
13
+ $sql = 'SELECT ocr_gocr, ocr_ocrad FROM ocrresults WHERE ocr_image = \'' . mysql_real_escape_string($db) . '\'';
14
+ $res = mysql_query($sql);
15
+ $row = mysql_fetch_assoc($res);
16
+
17
+?>
18
+<h2>OCR results</h2>
19
+<h3>gOCR</h3>
20
+<textarea><?PHP echo htmlentities($row['ocr_gocr']); ?></textarea>
21
+<h3>OCRad</h3>
22
+<textarea><?PHP echo htmlentities($row['ocr_ocrad']); ?></textarea>

+ 82
- 0
prog.php View File

@@ -0,0 +1,82 @@
1
+<?PHP
2
+
3
+/******************************************************************************\
4
+
5
+ thumb.php: Generate thumbnail image of specified file.
6
+ Copyright (C) 2004-2007 Chris 'MD87' Smith
7
+
8
+ This program is free software; you can redistribute it and/or
9
+ modify it under the terms of the GNU General Public License
10
+ as published by the Free Software Foundation; either version 2
11
+ of the License, or (at your option) any later version.
12
+
13
+ This program is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ GNU General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program; if not, write to the Free Software
20
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21
+
22
+|******************************************************************************|
23
+
24
+ Usage:
25
+ 
26
+  <img src="thumb.php?file_name.png"...>
27
+  
28
+  <img src="thumb.php?directory/file.jpg"...>
29
+  
30
+  <img src="thumb.php?http://www.example.com/file.jpg"...>
31
+
32
+
33
+\******************************************************************************/
34
+
35
+header('Cache-control: public');
36
+
37
+$image = $_SERVER['QUERY_STRING'];
38
+
39
+if (!file_exists($image)) {
40
+ 
41
+ /* TODO: Output error image. */
42
+ 
43
+ die();
44
+ 
45
+}
46
+
47
+if (file_exists('.thumbs/prog-' . md5($image))) {
48
+ $mtime = filemtime('.thumbs/prog-' . md5($image));
49
+ if ($mtime >= filemtime($image)) {
50
+  header('Content-type: image/jpeg');
51
+  readfile('.thumbs/prog-' . md5($image));
52
+  exit;
53
+ }
54
+}
55
+
56
+/* TODO: Optimise. */
57
+
58
+
59
+if (($imi = @imagecreatefromjpeg($image)) === FALSE) {
60
+ 
61
+ if (($imi = @imagecreatefrompng($image)) === FALSE) {
62
+  
63
+  if (($imi = @imagecreatefromgif($image)) === FALSE) {
64
+   
65
+   /* TODO: Output error image. */
66
+   
67
+   die();
68
+   
69
+  }
70
+  
71
+ }
72
+ 
73
+}
74
+
75
+
76
+header('Content-type: image/jpeg');
77
+imageinterlace($imi, true);
78
+
79
+imagejpeg($imi, '.thumbs/prog-' . md5($image), 100);
80
+imagejpeg($imi, false, 100);
81
+
82
+?>

+ 136
- 0
res/builder.js View File

@@ -0,0 +1,136 @@
1
+// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//
5
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
6
+// For details, see the script.aculo.us web site: http://script.aculo.us/
7
+
8
+var Builder = {
9
+  NODEMAP: {
10
+    AREA: 'map',
11
+    CAPTION: 'table',
12
+    COL: 'table',
13
+    COLGROUP: 'table',
14
+    LEGEND: 'fieldset',
15
+    OPTGROUP: 'select',
16
+    OPTION: 'select',
17
+    PARAM: 'object',
18
+    TBODY: 'table',
19
+    TD: 'table',
20
+    TFOOT: 'table',
21
+    TH: 'table',
22
+    THEAD: 'table',
23
+    TR: 'table'
24
+  },
25
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26
+  //       due to a Firefox bug
27
+  node: function(elementName) {
28
+    elementName = elementName.toUpperCase();
29
+    
30
+    // try innerHTML approach
31
+    var parentTag = this.NODEMAP[elementName] || 'div';
32
+    var parentElement = document.createElement(parentTag);
33
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35
+    } catch(e) {}
36
+    var element = parentElement.firstChild || null;
37
+      
38
+    // see if browser added wrapping tags
39
+    if(element && (element.tagName.toUpperCase() != elementName))
40
+      element = element.getElementsByTagName(elementName)[0];
41
+    
42
+    // fallback to createElement approach
43
+    if(!element) element = document.createElement(elementName);
44
+    
45
+    // abort if nothing could be created
46
+    if(!element) return;
47
+
48
+    // attributes (or text)
49
+    if(arguments[1])
50
+      if(this._isStringOrNumber(arguments[1]) ||
51
+        (arguments[1] instanceof Array) ||
52
+        arguments[1].tagName) {
53
+          this._children(element, arguments[1]);
54
+        } else {
55
+          var attrs = this._attributes(arguments[1]);
56
+          if(attrs.length) {
57
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
58
+              parentElement.innerHTML = "<" +elementName + " " +
59
+                attrs + "></" + elementName + ">";
60
+            } catch(e) {}
61
+            element = parentElement.firstChild || null;
62
+            // workaround firefox 1.0.X bug
63
+            if(!element) {
64
+              element = document.createElement(elementName);
65
+              for(attr in arguments[1]) 
66
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
67
+            }
68
+            if(element.tagName.toUpperCase() != elementName)
69
+              element = parentElement.getElementsByTagName(elementName)[0];
70
+          }
71
+        } 
72
+
73
+    // text, or array of children
74
+    if(arguments[2])
75
+      this._children(element, arguments[2]);
76
+
77
+     return element;
78
+  },
79
+  _text: function(text) {
80
+     return document.createTextNode(text);
81
+  },
82
+
83
+  ATTR_MAP: {
84
+    'className': 'class',
85
+    'htmlFor': 'for'
86
+  },
87
+
88
+  _attributes: function(attributes) {
89
+    var attrs = [];
90
+    for(attribute in attributes)
91
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
92
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
93
+    return attrs.join(" ");
94
+  },
95
+  _children: function(element, children) {
96
+    if(children.tagName) {
97
+      element.appendChild(children);
98
+      return;
99
+    }
100
+    if(typeof children=='object') { // array can hold nodes and text
101
+      children.flatten().each( function(e) {
102
+        if(typeof e=='object')
103
+          element.appendChild(e)
104
+        else
105
+          if(Builder._isStringOrNumber(e))
106
+            element.appendChild(Builder._text(e));
107
+      });
108
+    } else
109
+      if(Builder._isStringOrNumber(children))
110
+        element.appendChild(Builder._text(children));
111
+  },
112
+  _isStringOrNumber: function(param) {
113
+    return(typeof param=='string' || typeof param=='number');
114
+  },
115
+  build: function(html) {
116
+    var element = this.node('div');
117
+    $(element).update(html.strip());
118
+    return element.down();
119
+  },
120
+  dump: function(scope) { 
121
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
122
+  
123
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
124
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
125
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
126
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
127
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
128
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
129
+  
130
+    tags.each( function(tag){ 
131
+      scope[tag] = function() { 
132
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
133
+      } 
134
+    });
135
+  }
136
+}

+ 965
- 0
res/controls.js View File

@@ -0,0 +1,965 @@
1
+// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
5
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
6
+// Contributors:
7
+//  Richard Livsey
8
+//  Rahul Bhargava
9
+//  Rob Wills
10
+// 
11
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
12
+// For details, see the script.aculo.us web site: http://script.aculo.us/
13
+
14
+// Autocompleter.Base handles all the autocompletion functionality 
15
+// that's independent of the data source for autocompletion. This
16
+// includes drawing the autocompletion menu, observing keyboard
17
+// and mouse events, and similar.
18
+//
19
+// Specific autocompleters need to provide, at the very least, 
20
+// a getUpdatedChoices function that will be invoked every time
21
+// the text inside the monitored textbox changes. This method 
22
+// should get the text for which to provide autocompletion by
23
+// invoking this.getToken(), NOT by directly accessing
24
+// this.element.value. This is to allow incremental tokenized
25
+// autocompletion. Specific auto-completion logic (AJAX, etc)
26
+// belongs in getUpdatedChoices.
27
+//
28
+// Tokenized incremental autocompletion is enabled automatically
29
+// when an autocompleter is instantiated with the 'tokens' option
30
+// in the options parameter, e.g.:
31
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
32
+// will incrementally autocomplete with a comma as the token.
33
+// Additionally, ',' in the above example can be replaced with
34
+// a token array, e.g. { tokens: [',', '\n'] } which
35
+// enables autocompletion on multiple tokens. This is most 
36
+// useful when one of the tokens is \n (a newline), as it 
37
+// allows smart autocompletion after linebreaks.
38
+
39
+if(typeof Effect == 'undefined')
40
+  throw("controls.js requires including script.aculo.us' effects.js library");
41
+
42
+var Autocompleter = { }
43
+Autocompleter.Base = Class.create({
44
+  baseInitialize: function(element, update, options) {
45
+    element          = $(element)
46
+    this.element     = element; 
47
+    this.update      = $(update);  
48
+    this.hasFocus    = false; 
49
+    this.changed     = false; 
50
+    this.active      = false; 
51
+    this.index       = 0;     
52
+    this.entryCount  = 0;
53
+    this.oldElementValue = this.element.value;
54
+
55
+    if(this.setOptions)
56
+      this.setOptions(options);
57
+    else
58
+      this.options = options || { };
59
+
60
+    this.options.paramName    = this.options.paramName || this.element.name;
61
+    this.options.tokens       = this.options.tokens || [];
62
+    this.options.frequency    = this.options.frequency || 0.4;
63
+    this.options.minChars     = this.options.minChars || 1;
64
+    this.options.onShow       = this.options.onShow || 
65
+      function(element, update){ 
66
+        if(!update.style.position || update.style.position=='absolute') {
67
+          update.style.position = 'absolute';
68
+          Position.clone(element, update, {
69
+            setHeight: false, 
70
+            offsetTop: element.offsetHeight
71
+          });
72
+        }
73
+        Effect.Appear(update,{duration:0.15});
74
+      };
75
+    this.options.onHide = this.options.onHide || 
76
+      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
77
+
78
+    if(typeof(this.options.tokens) == 'string') 
79
+      this.options.tokens = new Array(this.options.tokens);
80
+    // Force carriage returns as token delimiters anyway
81
+    if (!this.options.tokens.include('\n'))
82
+      this.options.tokens.push('\n');
83
+
84
+    this.observer = null;
85
+    
86
+    this.element.setAttribute('autocomplete','off');
87
+
88
+    Element.hide(this.update);
89
+
90
+    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
91
+    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
92
+  },
93
+
94
+  show: function() {
95
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
96
+    if(!this.iefix && 
97
+      (Prototype.Browser.IE) &&
98
+      (Element.getStyle(this.update, 'position')=='absolute')) {
99
+      new Insertion.After(this.update, 
100
+       '<iframe id="' + this.update.id + '_iefix" '+
101
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
102
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
103
+      this.iefix = $(this.update.id+'_iefix');
104
+    }
105
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
106
+  },
107
+  
108
+  fixIEOverlapping: function() {
109
+    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
110
+    this.iefix.style.zIndex = 1;
111
+    this.update.style.zIndex = 2;
112
+    Element.show(this.iefix);
113
+  },
114
+
115
+  hide: function() {
116
+    this.stopIndicator();
117
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
118
+    if(this.iefix) Element.hide(this.iefix);
119
+  },
120
+
121
+  startIndicator: function() {
122
+    if(this.options.indicator) Element.show(this.options.indicator);
123
+  },
124
+
125
+  stopIndicator: function() {
126
+    if(this.options.indicator) Element.hide(this.options.indicator);
127
+  },
128
+
129
+  onKeyPress: function(event) {
130
+    if(this.active)
131
+      switch(event.keyCode) {
132
+       case Event.KEY_TAB:
133
+       case Event.KEY_RETURN:
134
+         this.selectEntry();
135
+         Event.stop(event);
136
+       case Event.KEY_ESC:
137
+         this.hide();
138
+         this.active = false;
139
+         Event.stop(event);
140
+         return;
141
+       case Event.KEY_LEFT:
142
+       case Event.KEY_RIGHT:
143
+         return;
144
+       case Event.KEY_UP:
145
+         this.markPrevious();
146
+         this.render();
147
+         Event.stop(event);
148
+         return;
149
+       case Event.KEY_DOWN:
150
+         this.markNext();
151
+         this.render();
152
+         Event.stop(event);
153
+         return;
154
+      }
155
+     else 
156
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
157
+         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
158
+
159
+    this.changed = true;
160
+    this.hasFocus = true;
161
+
162
+    if(this.observer) clearTimeout(this.observer);
163
+      this.observer = 
164
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
165
+  },
166
+
167
+  activate: function() {
168
+    this.changed = false;
169
+    this.hasFocus = true;
170
+    this.getUpdatedChoices();
171
+  },
172
+
173
+  onHover: function(event) {
174
+    var element = Event.findElement(event, 'LI');
175
+    if(this.index != element.autocompleteIndex) 
176
+    {
177
+        this.index = element.autocompleteIndex;
178
+        this.render();
179
+    }
180
+    Event.stop(event);
181
+  },
182
+  
183
+  onClick: function(event) {
184
+    var element = Event.findElement(event, 'LI');
185
+    this.index = element.autocompleteIndex;
186
+    this.selectEntry();
187
+    this.hide();
188
+  },
189
+  
190
+  onBlur: function(event) {
191
+    // needed to make click events working
192
+    setTimeout(this.hide.bind(this), 250);
193
+    this.hasFocus = false;
194
+    this.active = false;     
195
+  }, 
196
+  
197
+  render: function() {
198
+    if(this.entryCount > 0) {
199
+      for (var i = 0; i < this.entryCount; i++)
200
+        this.index==i ? 
201
+          Element.addClassName(this.getEntry(i),"selected") : 
202
+          Element.removeClassName(this.getEntry(i),"selected");
203
+      if(this.hasFocus) { 
204
+        this.show();
205
+        this.active = true;
206
+      }
207
+    } else {
208
+      this.active = false;
209
+      this.hide();
210
+    }
211
+  },
212
+  
213
+  markPrevious: function() {
214
+    if(this.index > 0) this.index--
215
+      else this.index = this.entryCount-1;
216
+    this.getEntry(this.index).scrollIntoView(true);
217
+  },
218
+  
219
+  markNext: function() {
220
+    if(this.index < this.entryCount-1) this.index++
221
+      else this.index = 0;
222
+    this.getEntry(this.index).scrollIntoView(false);
223
+  },
224
+  
225
+  getEntry: function(index) {
226
+    return this.update.firstChild.childNodes[index];
227
+  },
228
+  
229
+  getCurrentEntry: function() {
230
+    return this.getEntry(this.index);
231
+  },
232
+  
233
+  selectEntry: function() {
234
+    this.active = false;
235
+    this.updateElement(this.getCurrentEntry());
236
+  },
237
+
238
+  updateElement: function(selectedElement) {
239
+    if (this.options.updateElement) {
240
+      this.options.updateElement(selectedElement);
241
+      return;
242
+    }
243
+    var value = '';
244
+    if (this.options.select) {
245
+      var nodes = $(selectedElement).select('.' + this.options.select) || [];
246
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
247
+    } else
248
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
249
+    
250
+    var bounds = this.getTokenBounds();
251
+    if (bounds[0] != -1) {
252
+      var newValue = this.element.value.substr(0, bounds[0]);
253
+      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
254
+      if (whitespace)
255
+        newValue += whitespace[0];
256
+      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
257
+    } else {
258
+      this.element.value = value;
259
+    }
260
+    this.oldElementValue = this.element.value;
261
+    this.element.focus();
262
+    
263
+    if (this.options.afterUpdateElement)
264
+      this.options.afterUpdateElement(this.element, selectedElement);
265
+  },
266
+
267
+  updateChoices: function(choices) {
268
+    if(!this.changed && this.hasFocus) {
269
+      this.update.innerHTML = choices;
270
+      Element.cleanWhitespace(this.update);
271
+      Element.cleanWhitespace(this.update.down());
272
+
273
+      if(this.update.firstChild && this.update.down().childNodes) {
274
+        this.entryCount = 
275
+          this.update.down().childNodes.length;
276
+        for (var i = 0; i < this.entryCount; i++) {
277
+          var entry = this.getEntry(i);
278
+          entry.autocompleteIndex = i;
279
+          this.addObservers(entry);
280
+        }
281
+      } else { 
282
+        this.entryCount = 0;
283
+      }
284
+
285
+      this.stopIndicator();
286
+      this.index = 0;
287
+      
288
+      if(this.entryCount==1 && this.options.autoSelect) {
289
+        this.selectEntry();
290
+        this.hide();
291
+      } else {
292
+        this.render();
293
+      }
294
+    }
295
+  },
296
+
297
+  addObservers: function(element) {
298
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
299
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
300
+  },
301
+
302
+  onObserverEvent: function() {
303
+    this.changed = false;   
304
+    this.tokenBounds = null;
305
+    if(this.getToken().length>=this.options.minChars) {
306
+      this.getUpdatedChoices();
307
+    } else {
308
+      this.active = false;
309
+      this.hide();
310
+    }
311
+    this.oldElementValue = this.element.value;
312
+  },
313
+
314
+  getToken: function() {
315
+    var bounds = this.getTokenBounds();
316
+    return this.element.value.substring(bounds[0], bounds[1]).strip();
317
+  },
318
+
319
+  getTokenBounds: function() {
320
+    if (null != this.tokenBounds) return this.tokenBounds;
321
+    var value = this.element.value;
322
+    if (value.strip().empty()) return [-1, 0];
323
+    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
324
+    var offset = (diff == this.oldElementValue.length ? 1 : 0);
325
+    var prevTokenPos = -1, nextTokenPos = value.length;
326
+    var tp;
327
+    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
328
+      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
329
+      if (tp > prevTokenPos) prevTokenPos = tp;
330
+      tp = value.indexOf(this.options.tokens[index], diff + offset);
331
+      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
332
+    }
333
+    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
334
+  }
335
+});
336
+
337
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
338
+  var boundary = Math.min(newS.length, oldS.length);
339
+  for (var index = 0; index < boundary; ++index)
340
+    if (newS[index] != oldS[index])
341
+      return index;
342
+  return boundary;
343
+};
344
+
345
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
346
+  initialize: function(element, update, url, options) {
347
+    this.baseInitialize(element, update, options);
348
+    this.options.asynchronous  = true;
349
+    this.options.onComplete    = this.onComplete.bind(this);
350
+    this.options.defaultParams = this.options.parameters || null;
351
+    this.url                   = url;
352
+  },
353
+
354
+  getUpdatedChoices: function() {
355
+    this.startIndicator();
356
+    
357
+    var entry = encodeURIComponent(this.options.paramName) + '=' + 
358
+      encodeURIComponent(this.getToken());
359
+
360
+    this.options.parameters = this.options.callback ?
361
+      this.options.callback(this.element, entry) : entry;
362
+
363
+    if(this.options.defaultParams) 
364
+      this.options.parameters += '&' + this.options.defaultParams;
365
+    
366
+    new Ajax.Request(this.url, this.options);
367
+  },
368
+
369
+  onComplete: function(request) {
370
+    this.updateChoices(request.responseText);
371
+  }
372
+});
373
+
374
+// The local array autocompleter. Used when you'd prefer to
375
+// inject an array of autocompletion options into the page, rather
376
+// than sending out Ajax queries, which can be quite slow sometimes.
377
+//
378
+// The constructor takes four parameters. The first two are, as usual,
379
+// the id of the monitored textbox, and id of the autocompletion menu.
380
+// The third is the array you want to autocomplete from, and the fourth
381
+// is the options block.
382
+//
383
+// Extra local autocompletion options:
384
+// - choices - How many autocompletion choices to offer
385
+//
386
+// - partialSearch - If false, the autocompleter will match entered
387
+//                    text only at the beginning of strings in the 
388
+//                    autocomplete array. Defaults to true, which will
389
+//                    match text at the beginning of any *word* in the
390
+//                    strings in the autocomplete array. If you want to
391
+//                    search anywhere in the string, additionally set
392
+//                    the option fullSearch to true (default: off).
393
+//
394
+// - fullSsearch - Search anywhere in autocomplete array strings.
395
+//
396
+// - partialChars - How many characters to enter before triggering
397
+//                   a partial match (unlike minChars, which defines
398
+//                   how many characters are required to do any match
399
+//                   at all). Defaults to 2.
400
+//
401
+// - ignoreCase - Whether to ignore case when autocompleting.
402
+//                 Defaults to true.
403
+//
404
+// It's possible to pass in a custom function as the 'selector' 
405
+// option, if you prefer to write your own autocompletion logic.
406
+// In that case, the other options above will not apply unless
407
+// you support them.
408
+
409
+Autocompleter.Local = Class.create(Autocompleter.Base, {
410
+  initialize: function(element, update, array, options) {
411
+    this.baseInitialize(element, update, options);
412
+    this.options.array = array;
413
+  },
414
+
415
+  getUpdatedChoices: function() {
416
+    this.updateChoices(this.options.selector(this));
417
+  },
418
+
419
+  setOptions: function(options) {
420
+    this.options = Object.extend({
421
+      choices: 10,
422
+      partialSearch: true,
423
+      partialChars: 2,
424
+      ignoreCase: true,
425
+      fullSearch: false,
426
+      selector: function(instance) {
427
+        var ret       = []; // Beginning matches
428
+        var partial   = []; // Inside matches
429
+        var entry     = instance.getToken();
430
+        var count     = 0;
431
+
432
+        for (var i = 0; i < instance.options.array.length &&  
433
+          ret.length < instance.options.choices ; i++) { 
434
+
435
+          var elem = instance.options.array[i];
436
+          var foundPos = instance.options.ignoreCase ? 
437
+            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
438
+            elem.indexOf(entry);
439
+
440
+          while (foundPos != -1) {
441
+            if (foundPos == 0 && elem.length != entry.length) { 
442
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
443
+                elem.substr(entry.length) + "</li>");
444
+              break;
445
+            } else if (entry.length >= instance.options.partialChars && 
446
+              instance.options.partialSearch && foundPos != -1) {
447
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
448
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
449
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
450
+                  foundPos + entry.length) + "</li>");
451
+                break;
452
+              }
453
+            }
454
+
455
+            foundPos = instance.options.ignoreCase ? 
456
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
457
+              elem.indexOf(entry, foundPos + 1);
458
+
459
+          }
460
+        }
461
+        if (partial.length)
462
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
463
+        return "<ul>" + ret.join('') + "</ul>";
464
+      }
465
+    }, options || { });
466
+  }
467
+});
468
+
469
+// AJAX in-place editor and collection editor
470
+// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
471
+
472
+// Use this if you notice weird scrolling problems on some browsers,
473
+// the DOM might be a bit confused when this gets called so do this
474
+// waits 1 ms (with setTimeout) until it does the activation
475
+Field.scrollFreeActivate = function(field) {
476
+  setTimeout(function() {
477
+    Field.activate(field);
478
+  }, 1);
479
+}
480
+
481
+Ajax.InPlaceEditor = Class.create({
482
+  initialize: function(element, url, options) {
483
+    this.url = url;
484
+    this.element = element = $(element);
485
+    this.prepareOptions();
486
+    this._controls = { };
487
+    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
488
+    Object.extend(this.options, options || { });
489
+    if (!this.options.formId && this.element.id) {
490
+      this.options.formId = this.element.id + '-inplaceeditor';
491
+      if ($(this.options.formId))
492
+        this.options.formId = '';
493
+    }
494
+    if (this.options.externalControl)
495
+      this.options.externalControl = $(this.options.externalControl);
496
+    if (!this.options.externalControl)
497
+      this.options.externalControlOnly = false;
498
+    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
499
+    this.element.title = this.options.clickToEditText;
500
+    this._boundCancelHandler = this.handleFormCancellation.bind(this);
501
+    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
502
+    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
503
+    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
504
+    this._boundWrapperHandler = this.wrapUp.bind(this);
505
+    this.registerListeners();
506
+  },
507
+  checkForEscapeOrReturn: function(e) {
508
+    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
509
+    if (Event.KEY_ESC == e.keyCode)
510
+      this.handleFormCancellation(e);
511
+    else if (Event.KEY_RETURN == e.keyCode)
512
+      this.handleFormSubmission(e);
513
+  },
514
+  createControl: function(mode, handler, extraClasses) {
515
+    var control = this.options[mode + 'Control'];
516
+    var text = this.options[mode + 'Text'];
517
+    if ('button' == control) {
518
+      var btn = document.createElement('input');
519
+      btn.type = 'submit';
520
+      btn.value = text;
521
+      btn.className = 'editor_' + mode + '_button';
522
+      if ('cancel' == mode)
523
+        btn.onclick = this._boundCancelHandler;
524
+      this._form.appendChild(btn);
525
+      this._controls[mode] = btn;
526
+    } else if ('link' == control) {
527
+      var link = document.createElement('a');
528
+      link.href = '#';
529
+      link.appendChild(document.createTextNode(text));
530
+      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
531
+      link.className = 'editor_' + mode + '_link';
532
+      if (extraClasses)
533
+        link.className += ' ' + extraClasses;
534
+      this._form.appendChild(link);
535
+      this._controls[mode] = link;
536
+    }
537
+  },
538
+  createEditField: function() {
539
+    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
540
+    var fld;
541
+    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
542
+      fld = document.createElement('input');
543
+      fld.type = 'text';
544
+      var size = this.options.size || this.options.cols || 0;
545
+      if (0 < size) fld.size = size;
546
+    } else {
547
+      fld = document.createElement('textarea');
548
+      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
549
+      fld.cols = this.options.cols || 40;
550
+    }
551
+    fld.name = this.options.paramName;
552
+    fld.value = text; // No HTML breaks conversion anymore
553
+    fld.className = 'editor_field';
554
+    if (this.options.submitOnBlur)
555
+      fld.onblur = this._boundSubmitHandler;
556
+    this._controls.editor = fld;
557
+    if (this.options.loadTextURL)
558
+      this.loadExternalText();
559
+    this._form.appendChild(this._controls.editor);
560
+  },
561
+  createForm: function() {
562
+    var ipe = this;
563
+    function addText(mode, condition) {
564
+      var text = ipe.options['text' + mode + 'Controls'];
565
+      if (!text || condition === false) return;
566
+      ipe._form.appendChild(document.createTextNode(text));
567
+    };
568
+    this._form = $(document.createElement('form'));
569
+    this._form.id = this.options.formId;
570
+    this._form.addClassName(this.options.formClassName);
571
+    this._form.onsubmit = this._boundSubmitHandler;
572
+    this.createEditField();
573
+    if ('textarea' == this._controls.editor.tagName.toLowerCase())
574
+      this._form.appendChild(document.createElement('br'));
575
+    if (this.options.onFormCustomization)
576
+      this.options.onFormCustomization(this, this._form);
577
+    addText('Before', this.options.okControl || this.options.cancelControl);
578
+    this.createControl('ok', this._boundSubmitHandler);
579
+    addText('Between', this.options.okControl && this.options.cancelControl);
580
+    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
581
+    addText('After', this.options.okControl || this.options.cancelControl);
582
+  },
583
+  destroy: function() {
584
+    if (this._oldInnerHTML)
585
+      this.element.innerHTML = this._oldInnerHTML;
586
+    this.leaveEditMode();
587
+    this.unregisterListeners();
588
+  },
589
+  enterEditMode: function(e) {
590
+    if (this._saving || this._editing) return;
591
+    this._editing = true;
592
+    this.triggerCallback('onEnterEditMode');
593
+    if (this.options.externalControl)
594
+      this.options.externalControl.hide();
595
+    this.element.hide();
596
+    this.createForm();
597
+    this.element.parentNode.insertBefore(this._form, this.element);
598
+    if (!this.options.loadTextURL)
599
+      this.postProcessEditField();
600
+    if (e) Event.stop(e);
601
+  },
602
+  enterHover: function(e) {
603
+    if (this.options.hoverClassName)
604
+      this.element.addClassName(this.options.hoverClassName);
605
+    if (this._saving) return;
606
+    this.triggerCallback('onEnterHover');
607
+  },
608
+  getText: function() {
609
+    return this.element.innerHTML;
610
+  },
611
+  handleAJAXFailure: function(transport) {
612
+    this.triggerCallback('onFailure', transport);
613
+    if (this._oldInnerHTML) {
614
+      this.element.innerHTML = this._oldInnerHTML;
615
+      this._oldInnerHTML = null;
616
+    }
617
+  },
618
+  handleFormCancellation: function(e) {
619
+    this.wrapUp();
620
+    if (e) Event.stop(e);
621
+  },
622
+  handleFormSubmission: function(e) {
623
+    var form = this._form;
624
+    var value = $F(this._controls.editor);
625
+    this.prepareSubmission();
626
+    var params = this.options.callback(form, value) || '';
627
+    if (Object.isString(params))
628
+      params = params.toQueryParams();
629
+    params.editorId = this.element.id;
630
+    if (this.options.htmlResponse) {
631
+      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
632
+      Object.extend(options, {
633
+        parameters: params,
634
+        onComplete: this._boundWrapperHandler,
635
+        onFailure: this._boundFailureHandler
636
+      });
637
+      new Ajax.Updater({ success: this.element }, this.url, options);
638
+    } else {
639
+      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
640
+      Object.extend(options, {
641
+        parameters: params,
642
+        onComplete: this._boundWrapperHandler,
643
+        onFailure: this._boundFailureHandler
644
+      });
645
+      new Ajax.Request(this.url, options);
646
+    }
647
+    if (e) Event.stop(e);
648
+  },
649
+  leaveEditMode: function() {
650
+    this.element.removeClassName(this.options.savingClassName);
651
+    this.removeForm();
652
+    this.leaveHover();
653
+    this.element.style.backgroundColor = this._originalBackground;
654
+    this.element.show();
655
+    if (this.options.externalControl)
656
+      this.options.externalControl.show();
657
+    this._saving = false;
658
+    this._editing = false;
659
+    this._oldInnerHTML = null;
660
+    this.triggerCallback('onLeaveEditMode');
661
+  },
662
+  leaveHover: function(e) {
663
+    if (this.options.hoverClassName)
664
+      this.element.removeClassName(this.options.hoverClassName);
665
+    if (this._saving) return;
666
+    this.triggerCallback('onLeaveHover');
667
+  },
668
+  loadExternalText: function() {
669
+    this._form.addClassName(this.options.loadingClassName);
670
+    this._controls.editor.disabled = true;
671
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
672
+    Object.extend(options, {
673
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
674
+      onComplete: Prototype.emptyFunction,
675
+      onSuccess: function(transport) {
676
+        this._form.removeClassName(this.options.loadingClassName);
677
+        var text = transport.responseText;
678
+        if (this.options.stripLoadedTextTags)
679
+          text = text.stripTags();
680
+        this._controls.editor.value = text;
681
+        this._controls.editor.disabled = false;
682
+        this.postProcessEditField();
683
+      }.bind(this),
684
+      onFailure: this._boundFailureHandler
685
+    });
686
+    new Ajax.Request(this.options.loadTextURL, options);
687
+  },
688
+  postProcessEditField: function() {
689
+    var fpc = this.options.fieldPostCreation;
690
+    if (fpc)
691
+      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
692
+  },
693
+  prepareOptions: function() {
694
+    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
695
+    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
696
+    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
697
+      Object.extend(this.options, defs);
698
+    }.bind(this));
699
+  },
700
+  prepareSubmission: function() {
701
+    this._saving = true;
702
+    this.removeForm();
703
+    this.leaveHover();
704
+    this.showSaving();
705
+  },
706
+  registerListeners: function() {
707
+    this._listeners = { };
708
+    var listener;
709
+    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
710
+      listener = this[pair.value].bind(this);
711
+      this._listeners[pair.key] = listener;
712
+      if (!this.options.externalControlOnly)
713
+        this.element.observe(pair.key, listener);
714
+      if (this.options.externalControl)
715
+        this.options.externalControl.observe(pair.key, listener);
716
+    }.bind(this));
717
+  },
718
+  removeForm: function() {
719
+    if (!this._form) return;
720
+    this._form.remove();
721
+    this._form = null;
722
+    this._controls = { };
723
+  },
724
+  showSaving: function() {
725
+    this._oldInnerHTML = this.element.innerHTML;
726
+    this.element.innerHTML = this.options.savingText;
727
+    this.element.addClassName(this.options.savingClassName);
728
+    this.element.style.backgroundColor = this._originalBackground;
729
+    this.element.show();
730
+  },
731
+  triggerCallback: function(cbName, arg) {
732
+    if ('function' == typeof this.options[cbName]) {
733
+      this.options[cbName](this, arg);
734
+    }
735
+  },
736
+  unregisterListeners: function() {
737
+    $H(this._listeners).each(function(pair) {
738
+      if (!this.options.externalControlOnly)
739
+        this.element.stopObserving(pair.key, pair.value);
740
+      if (this.options.externalControl)
741
+        this.options.externalControl.stopObserving(pair.key, pair.value);
742
+    }.bind(this));
743
+  },
744
+  wrapUp: function(transport) {
745
+    this.leaveEditMode();
746
+    // Can't use triggerCallback due to backward compatibility: requires
747
+    // binding + direct element
748
+    this._boundComplete(transport, this.element);
749
+  }
750
+});
751
+
752
+Object.extend(Ajax.InPlaceEditor.prototype, {
753
+  dispose: Ajax.InPlaceEditor.prototype.destroy
754
+});
755
+
756
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
757
+  initialize: function($super, element, url, options) {
758
+    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
759
+    $super(element, url, options);
760
+  },
761
+
762
+  createEditField: function() {
763
+    var list = document.createElement('select');
764
+    list.name = this.options.paramName;
765
+    list.size = 1;
766
+    this._controls.editor = list;
767
+    this._collection = this.options.collection || [];
768
+    if (this.options.loadCollectionURL)
769
+      this.loadCollection();
770
+    else
771
+      this.checkForExternalText();
772
+    this._form.appendChild(this._controls.editor);
773
+  },
774
+
775
+  loadCollection: function() {
776
+    this._form.addClassName(this.options.loadingClassName);
777
+    this.showLoadingText(this.options.loadingCollectionText);
778
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
779
+    Object.extend(options, {
780
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
781
+      onComplete: Prototype.emptyFunction,
782
+      onSuccess: function(transport) {
783
+        var js = transport.responseText.strip();
784
+        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
785
+          throw 'Server returned an invalid collection representation.';
786
+        this._collection = eval(js);
787
+        this.checkForExternalText();
788
+      }.bind(this),
789
+      onFailure: this.onFailure
790
+    });
791
+    new Ajax.Request(this.options.loadCollectionURL, options);
792
+  },
793
+
794
+  showLoadingText: function(text) {
795
+    this._controls.editor.disabled = true;
796
+    var tempOption = this._controls.editor.firstChild;
797
+    if (!tempOption) {
798
+      tempOption = document.createElement('option');
799
+      tempOption.value = '';
800
+      this._controls.editor.appendChild(tempOption);
801
+      tempOption.selected = true;
802
+    }
803
+    tempOption.update((text || '').stripScripts().stripTags());
804
+  },
805
+
806
+  checkForExternalText: function() {
807
+    this._text = this.getText();
808
+    if (this.options.loadTextURL)
809
+      this.loadExternalText();
810
+    else
811
+      this.buildOptionList();
812
+  },
813
+
814
+  loadExternalText: function() {
815
+    this.showLoadingText(this.options.loadingText);
816
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
817
+    Object.extend(options, {
818
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
819
+      onComplete: Prototype.emptyFunction,
820
+      onSuccess: function(transport) {
821
+        this._text = transport.responseText.strip();
822
+        this.buildOptionList();
823
+      }.bind(this),
824
+      onFailure: this.onFailure
825
+    });
826
+    new Ajax.Request(this.options.loadTextURL, options);
827
+  },
828
+
829
+  buildOptionList: function() {
830
+    this._form.removeClassName(this.options.loadingClassName);
831
+    this._collection = this._collection.map(function(entry) {
832
+      return 2 === entry.length ? entry : [entry, entry].flatten();
833
+    });
834
+    var marker = ('value' in this.options) ? this.options.value : this._text;
835
+    var textFound = this._collection.any(function(entry) {
836
+      return entry[0] == marker;
837
+    }.bind(this));
838
+    this._controls.editor.update('');
839
+    var option;
840
+    this._collection.each(function(entry, index) {
841
+      option = document.createElement('option');
842
+      option.value = entry[0];
843
+      option.selected = textFound ? entry[0] == marker : 0 == index;
844
+      option.appendChild(document.createTextNode(entry[1]));
845
+      this._controls.editor.appendChild(option);
846
+    }.bind(this));
847
+    this._controls.editor.disabled = false;
848
+    Field.scrollFreeActivate(this._controls.editor);
849
+  }
850
+});
851
+
852
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
853
+//**** This only  exists for a while,  in order to  let ****
854
+//**** users adapt to  the new API.  Read up on the new ****
855
+//**** API and convert your code to it ASAP!            ****
856
+
857
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
858
+  if (!options) return;
859
+  function fallback(name, expr) {
860
+    if (name in options || expr === undefined) return;
861
+    options[name] = expr;
862
+  };
863
+  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
864
+    options.cancelLink == options.cancelButton == false ? false : undefined)));
865
+  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
866
+    options.okLink == options.okButton == false ? false : undefined)));
867
+  fallback('highlightColor', options.highlightcolor);
868
+  fallback('highlightEndColor', options.highlightendcolor);
869
+};
870
+
871
+Object.extend(Ajax.InPlaceEditor, {
872
+  DefaultOptions: {
873
+    ajaxOptions: { },
874
+    autoRows: 3,                                // Use when multi-line w/ rows == 1
875
+    cancelControl: 'link',                      // 'link'|'button'|false
876
+    cancelText: 'cancel',
877
+    clickToEditText: 'Click to edit',
878
+    externalControl: null,                      // id|elt
879
+    externalControlOnly: false,
880
+    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
881
+    formClassName: 'inplaceeditor-form',
882
+    formId: null,                               // id|elt
883
+    highlightColor: '#ffff99',
884
+    highlightEndColor: '#ffffff',
885
+    hoverClassName: '',
886
+    htmlResponse: true,
887
+    loadingClassName: 'inplaceeditor-loading',
888
+    loadingText: 'Loading...',
889
+    okControl: 'button',                        // 'link'|'button'|false
890
+    okText: 'ok',
891
+    paramName: 'value',
892
+    rows: 1,                                    // If 1 and multi-line, uses autoRows
893
+    savingClassName: 'inplaceeditor-saving',
894
+    savingText: 'Saving...',
895
+    size: 0,
896
+    stripLoadedTextTags: false,
897
+    submitOnBlur: false,
898
+    textAfterControls: '',
899
+    textBeforeControls: '',
900
+    textBetweenControls: ''
901
+  },
902
+  DefaultCallbacks: {
903
+    callback: function(form) {
904
+      return Form.serialize(form);
905
+    },
906
+    onComplete: function(transport, element) {
907
+      // For backward compatibility, this one is bound to the IPE, and passes
908
+      // the element directly.  It was too often customized, so we don't break it.
909
+      new Effect.Highlight(element, {
910
+        startcolor: this.options.highlightColor, keepBackgroundImage: true });
911
+    },
912
+    onEnterEditMode: null,
913
+    onEnterHover: function(ipe) {
914
+      ipe.element.style.backgroundColor = ipe.options.highlightColor;
915
+      if (ipe._effect)
916
+        ipe._effect.cancel();
917
+    },
918
+    onFailure: function(transport, ipe) {
919
+      alert('Error communication with the server: ' + transport.responseText.stripTags());
920
+    },
921
+    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
922
+    onLeaveEditMode: null,
923
+    onLeaveHover: function(ipe) {
924
+      ipe._effect = new Effect.Highlight(ipe.element, {
925
+        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
926
+        restorecolor: ipe._originalBackground, keepBackgroundImage: true
927
+      });
928
+    }
929
+  },
930
+  Listeners: {
931
+    click: 'enterEditMode',
932
+    keydown: 'checkForEscapeOrReturn',
933
+    mouseover: 'enterHover',
934
+    mouseout: 'leaveHover'
935
+  }
936
+});
937
+
938
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
939
+  loadingCollectionText: 'Loading options...'
940
+};
941
+
942
+// Delayed observer, like Form.Element.Observer, 
943
+// but waits for delay after last key input
944
+// Ideal for live-search fields
945
+
946
+Form.Element.DelayedObserver = Class.create({
947
+  initialize: function(element, delay, callback) {
948
+    this.delay     = delay || 0.5;
949
+    this.element   = $(element);
950
+    this.callback  = callback;
951
+    this.timer     = null;
952
+    this.lastValue = $F(this.element); 
953
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
954
+  },
955
+  delayedListener: function(event) {
956
+    if(this.lastValue == $F(this.element)) return;
957
+    if(this.timer) clearTimeout(this.timer);
958
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
959
+    this.lastValue = $F(this.element);
960
+  },
961
+  onTimerEvent: function() {
962
+    this.timer = null;
963
+    this.callback(this.element, $F(this.element));
964
+  }
965
+});

+ 52
- 0
res/dms.js View File

@@ -0,0 +1,52 @@
1
+var lastImage = null;
2
+var searchTO = null;
3
+
4
+function click(image) {
5
+ document.getElementById('previewimg').src = image.src.replace(/thumb/, 'prog');
6
+ document.getElementById('previewlink').href = document.getElementById('previewimg').src;
7
+
8
+ if (lastImage != null) {
9
+  lastImage.style.opacity = 1.0;
10
+ }
11
+
12
+ image.style.opacity = 0.5;
13
+ image.parentNode.style.backgroundColor = 'red';
14
+ lastImage = image;
15
+
16
+ var name = image.src.replace(/^.*?thumb\.php\?/, '');
17
+ new Ajax.Updater('info', 'info.php', {parameters: {image: name}, asynchronous: true});
18
+ new Ajax.Updater('ocr', 'ocr.php', {parameters: {image: name}, asynchronous: true});
19
+ new Ajax.Updater('logo', 'logo.php', {parameters: {image: name}, asynchronous: true});
20
+}
21
+
22
+function doSearch() {
23
+ if (searchTO != null) {
24
+  clearTimeout(searchTO);
25
+ }
26
+
27
+ $('search_throbber').style.visibility = 'visible';
28
+ searchTO = setTimeout(doSearch2, 1000);
29
+}
30
+
31
+function doSearch2() {
32
+ var term = $('search_text').value;
33
+ new Ajax.Updater('searchresults', 'search.php', {parameters: {query: term}, asynchronous: true, onComplete: doneSearch});
34
+}
35
+
36
+function doneSearch() {
37
+ $('search_throbber').style.visibility = 'hidden';
38
+}
39
+
40
+function showTab(what) {
41
+ $$('#previewtabs a').each(function(e) {
42
+  e.className = '';
43
+ });
44
+
45
+ $('preview_' + what).className = 'active';
46
+
47
+ $$('.preview').each(function(e) {
48
+  e.style.zIndex = 0;
49
+ });
50
+
51
+ $(what).style.zIndex = 10;
52
+}

+ 974
- 0
res/dragdrop.js View File

@@ -0,0 +1,974 @@
1
+// script.aculo.us dragdrop.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
5
+// 
6
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
7
+// For details, see the script.aculo.us web site: http://script.aculo.us/
8
+
9
+if(Object.isUndefined(Effect))
10
+  throw("dragdrop.js requires including script.aculo.us' effects.js library");
11
+
12
+var Droppables = {
13
+  drops: [],
14
+
15
+  remove: function(element) {
16
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
17
+  },
18
+
19
+  add: function(element) {
20
+    element = $(element);
21
+    var options = Object.extend({
22
+      greedy:     true,
23
+      hoverclass: null,
24
+      tree:       false
25
+    }, arguments[1] || { });
26
+
27
+    // cache containers
28
+    if(options.containment) {
29
+      options._containers = [];
30
+      var containment = options.containment;
31
+      if(Object.isArray(containment)) {
32
+        containment.each( function(c) { options._containers.push($(c)) });
33
+      } else {
34
+        options._containers.push($(containment));
35
+      }
36
+    }
37
+    
38
+    if(options.accept) options.accept = [options.accept].flatten();
39
+
40
+    Element.makePositioned(element); // fix IE
41
+    options.element = element;
42
+
43
+    this.drops.push(options);
44
+  },
45
+  
46
+  findDeepestChild: function(drops) {
47
+    deepest = drops[0];
48
+      
49
+    for (i = 1; i < drops.length; ++i)
50
+      if (Element.isParent(drops[i].element, deepest.element))
51
+        deepest = drops[i];
52
+    
53
+    return deepest;
54
+  },
55
+
56
+  isContained: function(element, drop) {
57
+    var containmentNode;
58
+    if(drop.tree) {
59
+      containmentNode = element.treeNode; 
60
+    } else {
61
+      containmentNode = element.parentNode;
62
+    }
63
+    return drop._containers.detect(function(c) { return containmentNode == c });
64
+  },
65
+  
66
+  isAffected: function(point, element, drop) {
67
+    return (
68
+      (drop.element!=element) &&
69
+      ((!drop._containers) ||
70
+        this.isContained(element, drop)) &&
71
+      ((!drop.accept) ||
72
+        (Element.classNames(element).detect( 
73
+          function(v) { return drop.accept.include(v) } ) )) &&
74
+      Position.within(drop.element, point[0], point[1]) );
75
+  },
76
+
77
+  deactivate: function(drop) {
78
+    if(drop.hoverclass)
79
+      Element.removeClassName(drop.element, drop.hoverclass);
80
+    this.last_active = null;
81
+  },
82
+
83
+  activate: function(drop) {
84
+    if(drop.hoverclass)
85
+      Element.addClassName(drop.element, drop.hoverclass);
86
+    this.last_active = drop;
87
+  },
88
+
89
+  show: function(point, element) {
90
+    if(!this.drops.length) return;
91
+    var drop, affected = [];
92
+    
93
+    this.drops.each( function(drop) {
94
+      if(Droppables.isAffected(point, element, drop))
95
+        affected.push(drop);
96
+    });
97
+        
98
+    if(affected.length>0)
99
+      drop = Droppables.findDeepestChild(affected);
100
+
101
+    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
102
+    if (drop) {
103
+      Position.within(drop.element, point[0], point[1]);
104
+      if(drop.onHover)
105
+        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
106
+      
107
+      if (drop != this.last_active) Droppables.activate(drop);
108
+    }
109
+  },
110
+
111
+  fire: function(event, element) {
112
+    if(!this.last_active) return;
113
+    Position.prepare();
114
+
115
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
116
+      if (this.last_active.onDrop) {
117
+        this.last_active.onDrop(element, this.last_active.element, event); 
118
+        return true; 
119
+      }
120
+  },
121
+
122
+  reset: function() {
123
+    if(this.last_active)
124
+      this.deactivate(this.last_active);
125
+  }
126
+}
127
+
128
+var Draggables = {
129
+  drags: [],
130
+  observers: [],
131
+  
132
+  register: function(draggable) {
133
+    if(this.drags.length == 0) {
134
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
135
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
136
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
137
+      
138
+      Event.observe(document, "mouseup", this.eventMouseUp);
139
+      Event.observe(document, "mousemove", this.eventMouseMove);
140
+      Event.observe(document, "keypress", this.eventKeypress);
141
+    }
142
+    this.drags.push(draggable);
143
+  },
144
+  
145
+  unregister: function(draggable) {
146
+    this.drags = this.drags.reject(function(d) { return d==draggable });
147
+    if(this.drags.length == 0) {
148
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
149
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
150
+      Event.stopObserving(document, "keypress", this.eventKeypress);
151
+    }
152
+  },
153
+  
154
+  activate: function(draggable) {
155
+    if(draggable.options.delay) { 
156
+      this._timeout = setTimeout(function() { 
157
+        Draggables._timeout = null; 
158
+        window.focus(); 
159
+        Draggables.activeDraggable = draggable; 
160
+      }.bind(this), draggable.options.delay); 
161
+    } else {
162
+      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
163
+      this.activeDraggable = draggable;
164
+    }
165
+  },
166
+  
167
+  deactivate: function() {
168
+    this.activeDraggable = null;
169
+  },
170
+  
171
+  updateDrag: function(event) {
172
+    if(!this.activeDraggable) return;
173
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
174
+    // Mozilla-based browsers fire successive mousemove events with
175
+    // the same coordinates, prevent needless redrawing (moz bug?)
176
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
177
+    this._lastPointer = pointer;
178
+    
179
+    this.activeDraggable.updateDrag(event, pointer);
180
+  },
181
+  
182
+  endDrag: function(event) {
183
+    if(this._timeout) { 
184
+      clearTimeout(this._timeout); 
185
+      this._timeout = null; 
186
+    }
187
+    if(!this.activeDraggable) return;
188
+    this._lastPointer = null;
189
+    this.activeDraggable.endDrag(event);
190
+    this.activeDraggable = null;
191
+  },
192
+  
193
+  keyPress: function(event) {
194
+    if(this.activeDraggable)
195
+      this.activeDraggable.keyPress(event);
196
+  },
197
+  
198
+  addObserver: function(observer) {
199
+    this.observers.push(observer);
200
+    this._cacheObserverCallbacks();
201
+  },
202
+  
203
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
204
+    this.observers = this.observers.reject( function(o) { return o.element==element });
205
+    this._cacheObserverCallbacks();
206
+  },
207
+  
208
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
209
+    if(this[eventName+'Count'] > 0)
210
+      this.observers.each( function(o) {
211
+        if(o[eventName]) o[eventName](eventName, draggable, event);
212
+      });
213
+    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
214
+  },
215
+  
216
+  _cacheObserverCallbacks: function() {
217
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
218
+      Draggables[eventName+'Count'] = Draggables.observers.select(
219
+        function(o) { return o[eventName]; }
220
+      ).length;
221
+    });
222
+  }
223
+}
224
+
225
+/*--------------------------------------------------------------------------*/
226
+
227
+var Draggable = Class.create({
228
+  initialize: function(element) {
229
+    var defaults = {
230
+      handle: false,
231
+      reverteffect: function(element, top_offset, left_offset) {
232
+        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
233
+        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
234
+          queue: {scope:'_draggable', position:'end'}
235
+        });
236
+      },
237
+      endeffect: function(element) {
238
+        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
239
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
240
+          queue: {scope:'_draggable', position:'end'},
241
+          afterFinish: function(){ 
242
+            Draggable._dragging[element] = false 
243
+          }
244
+        }); 
245
+      },
246
+      zindex: 1000,
247
+      revert: false,
248
+      quiet: false,
249
+      scroll: false,
250
+      scrollSensitivity: 20,
251
+      scrollSpeed: 15,
252
+      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
253
+      delay: 0
254
+    };
255
+    
256
+    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
257
+      Object.extend(defaults, {
258
+        starteffect: function(element) {
259
+          element._opacity = Element.getOpacity(element);
260
+          Draggable._dragging[element] = true;
261
+          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
262
+        }
263
+      });
264
+    
265
+    var options = Object.extend(defaults, arguments[1] || { });
266
+
267
+    this.element = $(element);
268
+    
269
+    if(options.handle && Object.isString(options.handle))
270
+      this.handle = this.element.down('.'+options.handle, 0);
271
+    
272
+    if(!this.handle) this.handle = $(options.handle);
273
+    if(!this.handle) this.handle = this.element;
274
+    
275
+    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
276
+      options.scroll = $(options.scroll);
277
+      this._isScrollChild = Element.childOf(this.element, options.scroll);
278
+    }
279
+
280
+    Element.makePositioned(this.element); // fix IE    
281
+
282
+    this.options  = options;
283
+    this.dragging = false;   
284
+
285
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
286
+    Event.observe(this.handle, "mousedown", this.eventMouseDown);
287
+    
288
+    Draggables.register(this);
289
+  },
290
+  
291
+  destroy: function() {
292
+    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
293
+    Draggables.unregister(this);
294
+  },
295
+  
296
+  currentDelta: function() {
297
+    return([
298
+      parseInt(Element.getStyle(this.element,'left') || '0'),
299
+      parseInt(Element.getStyle(this.element,'top') || '0')]);
300
+  },
301
+  
302
+  initDrag: function(event) {
303
+    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
304
+      Draggable._dragging[this.element]) return;
305
+    if(Event.isLeftClick(event)) {    
306
+      // abort on form elements, fixes a Firefox issue
307
+      var src = Event.element(event);
308
+      if((tag_name = src.tagName.toUpperCase()) && (
309
+        tag_name=='INPUT' ||
310
+        tag_name=='SELECT' ||
311
+        tag_name=='OPTION' ||
312
+        tag_name=='BUTTON' ||
313
+        tag_name=='TEXTAREA')) return;
314
+        
315
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
316
+      var pos     = Position.cumulativeOffset(this.element);
317
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
318
+      
319
+      Draggables.activate(this);
320
+      Event.stop(event);
321
+    }
322
+  },
323
+  
324
+  startDrag: function(event) {
325
+    this.dragging = true;
326
+    if(!this.delta)
327
+      this.delta = this.currentDelta();
328
+    
329
+    if(this.options.zindex) {
330
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
331
+      this.element.style.zIndex = this.options.zindex;
332
+    }
333
+    
334
+    if(this.options.ghosting) {
335
+      this._clone = this.element.cloneNode(true);
336
+      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
337
+      if (!this.element._originallyAbsolute)
338
+        Position.absolutize(this.element);
339
+      this.element.parentNode.insertBefore(this._clone, this.element);
340
+    }
341
+    
342
+    if(this.options.scroll) {
343
+      if (this.options.scroll == window) {
344
+        var where = this._getWindowScroll(this.options.scroll);
345
+        this.originalScrollLeft = where.left;
346
+        this.originalScrollTop = where.top;
347
+      } else {
348
+        this.originalScrollLeft = this.options.scroll.scrollLeft;
349
+        this.originalScrollTop = this.options.scroll.scrollTop;
350
+      }
351
+    }
352
+    
353
+    Draggables.notify('onStart', this, event);
354
+        
355
+    if(this.options.starteffect) this.options.starteffect(this.element);
356
+  },
357
+  
358
+  updateDrag: function(event, pointer) {
359
+    if(!this.dragging) this.startDrag(event);
360
+    
361
+    if(!this.options.quiet){
362
+      Position.prepare();
363
+      Droppables.show(pointer, this.element);
364
+    }
365
+    
366
+    Draggables.notify('onDrag', this, event);
367
+    
368
+    this.draw(pointer);
369
+    if(this.options.change) this.options.change(this);
370
+    
371
+    if(this.options.scroll) {
372
+      this.stopScrolling();
373
+      
374
+      var p;
375
+      if (this.options.scroll == window) {
376
+        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
377
+      } else {
378
+        p = Position.page(this.options.scroll);
379
+        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
380
+        p[1] += this.options.scroll.scrollTop + Position.deltaY;
381
+        p.push(p[0]+this.options.scroll.offsetWidth);
382
+        p.push(p[1]+this.options.scroll.offsetHeight);
383
+      }
384
+      var speed = [0,0];
385
+      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
386
+      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
387
+      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
388
+      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
389
+      this.startScrolling(speed);
390
+    }
391
+    
392
+    // fix AppleWebKit rendering
393
+    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
394
+    
395
+    Event.stop(event);
396
+  },
397
+  
398
+  finishDrag: function(event, success) {
399
+    this.dragging = false;
400
+    
401
+    if(this.options.quiet){
402
+      Position.prepare();
403
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
404
+      Droppables.show(pointer, this.element);
405
+    }
406
+
407
+    if(this.options.ghosting) {
408
+      if (!this.element._originallyAbsolute)
409
+        Position.relativize(this.element);
410
+      delete this.element._originallyAbsolute;
411
+      Element.remove(this._clone);
412
+      this._clone = null;
413
+    }
414
+
415
+    var dropped = false; 
416
+    if(success) { 
417
+      dropped = Droppables.fire(event, this.element); 
418
+      if (!dropped) dropped = false; 
419
+    }
420
+    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
421
+    Draggables.notify('onEnd', this, event);
422
+
423
+    var revert = this.options.revert;
424
+    if(revert && Object.isFunction(revert)) revert = revert(this.element);
425
+    
426
+    var d = this.currentDelta();
427
+    if(revert && this.options.reverteffect) {
428
+      if (dropped == 0 || revert != 'failure')
429
+        this.options.reverteffect(this.element,
430
+          d[1]-this.delta[1], d[0]-this.delta[0]);
431
+    } else {
432
+      this.delta = d;
433
+    }
434
+
435
+    if(this.options.zindex)
436
+      this.element.style.zIndex = this.originalZ;
437
+
438
+    if(this.options.endeffect) 
439
+      this.options.endeffect(this.element);
440
+      
441
+    Draggables.deactivate(this);
442
+    Droppables.reset();
443
+  },
444
+  
445
+  keyPress: function(event) {
446
+    if(event.keyCode!=Event.KEY_ESC) return;
447
+    this.finishDrag(event, false);
448
+    Event.stop(event);
449
+  },
450
+  
451
+  endDrag: function(event) {
452
+    if(!this.dragging) return;
453
+    this.stopScrolling();
454
+    this.finishDrag(event, true);
455
+    Event.stop(event);
456
+  },
457
+  
458
+  draw: function(point) {
459
+    var pos = Position.cumulativeOffset(this.element);
460
+    if(this.options.ghosting) {
461
+      var r   = Position.realOffset(this.element);
462
+      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
463
+    }
464
+    
465
+    var d = this.currentDelta();
466
+    pos[0] -= d[0]; pos[1] -= d[1];
467
+    
468
+    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
469
+      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
470
+      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
471
+    }
472
+    
473
+    var p = [0,1].map(function(i){ 
474
+      return (point[i]-pos[i]-this.offset[i]) 
475
+    }.bind(this));
476
+    
477
+    if(this.options.snap) {
478
+      if(Object.isFunction(this.options.snap)) {
479
+        p = this.options.snap(p[0],p[1],this);
480
+      } else {
481
+      if(Object.isArray(this.options.snap)) {
482
+        p = p.map( function(v, i) {
483
+          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
484
+      } else {
485
+        p = p.map( function(v) {
486
+          return (v/this.options.snap).round()*this.options.snap }.bind(this))
487
+      }
488
+    }}
489
+    
490
+    var style = this.element.style;
491
+    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
492
+      style.left = p[0] + "px";
493
+    if((!this.options.constraint) || (this.options.constraint=='vertical'))
494
+      style.top  = p[1] + "px";
495
+    
496
+    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
497
+  },
498
+  
499
+  stopScrolling: function() {
500
+    if(this.scrollInterval) {
501
+      clearInterval(this.scrollInterval);
502
+      this.scrollInterval = null;
503
+      Draggables._lastScrollPointer = null;
504
+    }
505
+  },
506
+  
507
+  startScrolling: function(speed) {
508
+    if(!(speed[0] || speed[1])) return;
509
+    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
510
+    this.lastScrolled = new Date();
511
+    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
512
+  },
513
+  
514
+  scroll: function() {
515
+    var current = new Date();
516
+    var delta = current - this.lastScrolled;
517
+    this.lastScrolled = current;
518
+    if(this.options.scroll == window) {
519
+      with (this._getWindowScroll(this.options.scroll)) {
520
+        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
521
+          var d = delta / 1000;
522
+          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
523
+        }
524
+      }
525
+    } else {
526
+      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
527
+      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
528
+    }
529
+    
530
+    Position.prepare();
531
+    Droppables.show(Draggables._lastPointer, this.element);
532
+    Draggables.notify('onDrag', this);
533
+    if (this._isScrollChild) {
534
+      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
535
+      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
536
+      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
537
+      if (Draggables._lastScrollPointer[0] < 0)
538
+        Draggables._lastScrollPointer[0] = 0;
539
+      if (Draggables._lastScrollPointer[1] < 0)
540
+        Draggables._lastScrollPointer[1] = 0;
541
+      this.draw(Draggables._lastScrollPointer);
542
+    }
543
+    
544
+    if(this.options.change) this.options.change(this);
545
+  },
546
+  
547
+  _getWindowScroll: function(w) {
548
+    var T, L, W, H;
549
+    with (w.document) {
550
+      if (w.document.documentElement && documentElement.scrollTop) {
551
+        T = documentElement.scrollTop;
552
+        L = documentElement.scrollLeft;
553
+      } else if (w.document.body) {
554
+        T = body.scrollTop;
555
+        L = body.scrollLeft;
556
+      }
557
+      if (w.innerWidth) {
558
+        W = w.innerWidth;
559
+        H = w.innerHeight;
560
+      } else if (w.document.documentElement && documentElement.clientWidth) {
561
+        W = documentElement.clientWidth;
562
+        H = documentElement.clientHeight;
563
+      } else {
564
+        W = body.offsetWidth;
565
+        H = body.offsetHeight
566
+      }
567
+    }
568
+    return { top: T, left: L, width: W, height: H };
569
+  }
570
+});
571
+
572
+Draggable._dragging = { };
573
+
574
+/*--------------------------------------------------------------------------*/
575
+
576
+var SortableObserver = Class.create({
577
+  initialize: function(element, observer) {
578
+    this.element   = $(element);
579
+    this.observer  = observer;
580
+    this.lastValue = Sortable.serialize(this.element);
581
+  },
582
+  
583
+  onStart: function() {
584
+    this.lastValue = Sortable.serialize(this.element);
585
+  },
586
+  
587
+  onEnd: function() {
588
+    Sortable.unmark();
589
+    if(this.lastValue != Sortable.serialize(this.element))
590
+      this.observer(this.element)
591
+  }
592
+});
593
+
594
+var Sortable = {
595
+  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
596
+  
597
+  sortables: { },
598
+  
599
+  _findRootElement: function(element) {
600
+    while (element.tagName.toUpperCase() != "BODY") {  
601
+      if(element.id && Sortable.sortables[element.id]) return element;
602
+      element = element.parentNode;
603
+    }
604
+  },
605
+
606
+  options: function(element) {
607
+    element = Sortable._findRootElement($(element));
608
+    if(!element) return;
609
+    return Sortable.sortables[element.id];
610
+  },
611
+  
612
+  destroy: function(element){
613
+    var s = Sortable.options(element);
614
+    
615
+    if(s) {
616
+      Draggables.removeObserver(s.element);
617
+      s.droppables.each(function(d){ Droppables.remove(d) });
618
+      s.draggables.invoke('destroy');
619
+      
620
+      delete Sortable.sortables[s.element.id];
621
+    }
622
+  },
623
+
624
+  create: function(element) {
625
+    element = $(element);
626
+    var options = Object.extend({ 
627
+      element:     element,
628
+      tag:         'li',       // assumes li children, override with tag: 'tagname'
629
+      dropOnEmpty: false,
630
+      tree:        false,
631
+      treeTag:     'ul',
632
+      overlap:     'vertical', // one of 'vertical', 'horizontal'
633
+      constraint:  'vertical', // one of 'vertical', 'horizontal', false
634
+      containment: element,    // also takes array of elements (or id's); or false
635
+      handle:      false,      // or a CSS class
636
+      only:        false,
637
+      delay:       0,
638
+      hoverclass:  null,
639
+      ghosting:    false,
640
+      quiet:       false, 
641
+      scroll:      false,
642
+      scrollSensitivity: 20,
643
+      scrollSpeed: 15,
644
+      format:      this.SERIALIZE_RULE,
645
+      
646
+      // these take arrays of elements or ids and can be 
647
+      // used for better initialization performance
648
+      elements:    false,
649
+      handles:     false,
650
+      
651
+      onChange:    Prototype.emptyFunction,
652
+      onUpdate:    Prototype.emptyFunction
653
+    }, arguments[1] || { });
654
+
655
+    // clear any old sortable with same element
656
+    this.destroy(element);
657
+
658
+    // build options for the draggables
659
+    var options_for_draggable = {
660
+      revert:      true,
661
+      quiet:       options.quiet,
662
+      scroll:      options.scroll,
663
+      scrollSpeed: options.scrollSpeed,
664
+      scrollSensitivity: options.scrollSensitivity,
665
+      delay:       options.delay,
666
+      ghosting:    options.ghosting,
667
+      constraint:  options.constraint,
668
+      handle:      options.handle };
669
+
670
+    if(options.starteffect)
671
+      options_for_draggable.starteffect = options.starteffect;
672
+
673
+    if(options.reverteffect)
674
+      options_for_draggable.reverteffect = options.reverteffect;
675
+    else
676
+      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
677
+        element.style.top  = 0;
678
+        element.style.left = 0;
679
+      };
680
+
681
+    if(options.endeffect)
682
+      options_for_draggable.endeffect = options.endeffect;
683
+
684
+    if(options.zindex)
685
+      options_for_draggable.zindex = options.zindex;
686
+
687
+    // build options for the droppables  
688
+    var options_for_droppable = {
689
+      overlap:     options.overlap,
690
+      containment: options.containment,
691
+      tree:        options.tree,
692
+      hoverclass:  options.hoverclass,
693
+      onHover:     Sortable.onHover
694
+    }
695
+    
696
+    var options_for_tree = {
697
+      onHover:      Sortable.onEmptyHover,
698
+      overlap:      options.overlap,
699
+      containment:  options.containment,
700
+      hoverclass:   options.hoverclass
701
+    }
702
+
703
+    // fix for gecko engine
704
+    Element.cleanWhitespace(element); 
705
+
706
+    options.draggables = [];
707
+    options.droppables = [];
708
+
709
+    // drop on empty handling
710
+    if(options.dropOnEmpty || options.tree) {
711
+      Droppables.add(element, options_for_tree);
712
+      options.droppables.push(element);
713
+    }
714
+
715
+    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
716
+      var handle = options.handles ? $(options.handles[i]) :
717
+        (options.handle ? $(e).select('.' + options.handle)[0] : e); 
718
+      options.draggables.push(
719
+        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
720
+      Droppables.add(e, options_for_droppable);
721
+      if(options.tree) e.treeNode = element;
722
+      options.droppables.push(e);      
723
+    });
724
+    
725
+    if(options.tree) {
726
+      (Sortable.findTreeElements(element, options) || []).each( function(e) {
727
+        Droppables.add(e, options_for_tree);
728
+        e.treeNode = element;
729
+        options.droppables.push(e);
730
+      });
731
+    }
732
+
733
+    // keep reference
734
+    this.sortables[element.id] = options;
735
+
736
+    // for onupdate
737
+    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
738
+
739
+  },
740
+
741
+  // return all suitable-for-sortable elements in a guaranteed order
742
+  findElements: function(element, options) {
743
+    return Element.findChildren(
744
+      element, options.only, options.tree ? true : false, options.tag);
745
+  },
746
+  
747
+  findTreeElements: function(element, options) {
748
+    return Element.findChildren(
749
+      element, options.only, options.tree ? true : false, options.treeTag);
750
+  },
751
+
752
+  onHover: function(element, dropon, overlap) {
753
+    if(Element.isParent(dropon, element)) return;
754
+
755
+    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
756
+      return;
757
+    } else if(overlap>0.5) {
758
+      Sortable.mark(dropon, 'before');
759
+      if(dropon.previousSibling != element) {
760
+        var oldParentNode = element.parentNode;
761
+        element.style.visibility = "hidden"; // fix gecko rendering
762
+        dropon.parentNode.insertBefore(element, dropon);
763
+        if(dropon.parentNode!=oldParentNode) 
764
+          Sortable.options(oldParentNode).onChange(element);
765
+        Sortable.options(dropon.parentNode).onChange(element);
766
+      }
767
+    } else {
768
+      Sortable.mark(dropon, 'after');
769
+      var nextElement = dropon.nextSibling || null;
770
+      if(nextElement != element) {
771
+        var oldParentNode = element.parentNode;
772
+        element.style.visibility = "hidden"; // fix gecko rendering
773
+        dropon.parentNode.insertBefore(element, nextElement);
774
+        if(dropon.parentNode!=oldParentNode) 
775
+          Sortable.options(oldParentNode).onChange(element);
776
+        Sortable.options(dropon.parentNode).onChange(element);
777
+      }
778
+    }
779
+  },
780
+  
781
+  onEmptyHover: function(element, dropon, overlap) {
782
+    var oldParentNode = element.parentNode;
783
+    var droponOptions = Sortable.options(dropon);
784
+        
785
+    if(!Element.isParent(dropon, element)) {
786
+      var index;
787
+      
788
+      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
789
+      var child = null;
790
+            
791
+      if(children) {
792
+        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
793
+        
794
+        for (index = 0; index < children.length; index += 1) {
795
+          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
796
+            offset -= Element.offsetSize (children[index], droponOptions.overlap);
797
+          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
798
+            child = index + 1 < children.length ? children[index + 1] : null;
799
+            break;
800
+          } else {
801
+            child = children[index];
802
+            break;
803
+          }
804
+        }
805
+      }
806
+      
807
+      dropon.insertBefore(element, child);
808
+      
809
+      Sortable.options(oldParentNode).onChange(element);
810
+      droponOptions.onChange(element);
811
+    }
812
+  },
813
+
814
+  unmark: function() {
815
+    if(Sortable._marker) Sortable._marker.hide();
816
+  },
817
+
818
+  mark: function(dropon, position) {
819
+    // mark on ghosting only
820
+    var sortable = Sortable.options(dropon.parentNode);
821
+    if(sortable && !sortable.ghosting) return; 
822
+
823
+    if(!Sortable._marker) {
824
+      Sortable._marker = 
825
+        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
826
+          hide().addClassName('dropmarker').setStyle({position:'absolute'});
827
+      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
828
+    }    
829
+    var offsets = Position.cumulativeOffset(dropon);
830
+    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
831
+    
832
+    if(position=='after')
833
+      if(sortable.overlap == 'horizontal') 
834
+        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
835
+      else
836
+        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
837
+    
838
+    Sortable._marker.show();
839
+  },
840
+  
841
+  _tree: function(element, options, parent) {
842
+    var children = Sortable.findElements(element, options) || [];
843
+  
844
+    for (var i = 0; i < children.length; ++i) {
845
+      var match = children[i].id.match(options.format);
846
+
847
+      if (!match) continue;
848
+      
849
+      var child = {
850
+        id: encodeURIComponent(match ? match[1] : null),
851
+        element: element,
852
+        parent: parent,
853
+        children: [],
854
+        position: parent.children.length,
855
+        container: $(children[i]).down(options.treeTag)
856
+      }
857
+      
858
+      /* Get the element containing the children and recurse over it */
859
+      if (child.container)
860
+        this._tree(child.container, options, child)
861
+      
862
+      parent.children.push (child);
863
+    }
864
+
865
+    return parent; 
866
+  },
867
+
868
+  tree: function(element) {
869
+    element = $(element);
870
+    var sortableOptions = this.options(element);
871
+    var options = Object.extend({
872
+      tag: sortableOptions.tag,
873
+      treeTag: sortableOptions.treeTag,
874
+      only: sortableOptions.only,
875
+      name: element.id,
876
+      format: sortableOptions.format
877
+    }, arguments[1] || { });
878
+    
879
+    var root = {
880
+      id: null,
881
+      parent: null,
882
+      children: [],
883
+      container: element,
884
+      position: 0
885
+    }
886
+    
887
+    return Sortable._tree(element, options, root);
888
+  },
889
+
890
+  /* Construct a [i] index for a particular node */
891
+  _constructIndex: function(node) {
892
+    var index = '';
893
+    do {
894
+      if (node.id) index = '[' + node.position + ']' + index;
895
+    } while ((node = node.parent) != null);
896
+    return index;
897
+  },
898
+
899
+  sequence: function(element) {
900
+    element = $(element);
901
+    var options = Object.extend(this.options(element), arguments[1] || { });
902
+    
903
+    return $(this.findElements(element, options) || []).map( function(item) {
904
+      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
905
+    });
906
+  },
907
+
908
+  setSequence: function(element, new_sequence) {
909
+    element = $(element);
910
+    var options = Object.extend(this.options(element), arguments[2] || { });
911
+    
912
+    var nodeMap = { };
913
+    this.findElements(element, options).each( function(n) {
914
+        if (n.id.match(options.format))
915
+            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
916
+        n.parentNode.removeChild(n);
917
+    });
918
+   
919
+    new_sequence.each(function(ident) {
920
+      var n = nodeMap[ident];
921
+      if (n) {
922
+        n[1].appendChild(n[0]);
923
+        delete nodeMap[ident];
924
+      }
925
+    });
926
+  },
927
+  
928
+  serialize: function(element) {
929
+    element = $(element);
930
+    var options = Object.extend(Sortable.options(element), arguments[1] || { });
931
+    var name = encodeURIComponent(
932
+      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
933
+    
934
+    if (options.tree) {
935
+      return Sortable.tree(element, arguments[1]).children.map( function (item) {
936
+        return [name + Sortable._constructIndex(item) + "[id]=" + 
937
+                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
938
+      }).flatten().join('&');
939
+    } else {
940
+      return Sortable.sequence(element, arguments[1]).map( function(item) {
941
+        return name + "[]=" + encodeURIComponent(item);
942
+      }).join('&');
943
+    }
944
+  }
945
+}
946
+
947
+// Returns true if child is contained within element
948
+Element.isParent = function(child, element) {
949
+  if (!child.parentNode || child == element) return false;
950
+  if (child.parentNode == element) return true;
951
+  return Element.isParent(child.parentNode, element);
952
+}
953
+
954
+Element.findChildren = function(element, only, recursive, tagName) {   
955
+  if(!element.hasChildNodes()) return null;
956
+  tagName = tagName.toUpperCase();
957
+  if(only) only = [only].flatten();
958
+  var elements = [];
959
+  $A(element.childNodes).each( function(e) {
960
+    if(e.tagName && e.tagName.toUpperCase()==tagName &&
961
+      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
962
+        elements.push(e);
963
+    if(recursive) {
964
+      var grandchildren = Element.findChildren(e, only, recursive, tagName);
965
+      if(grandchildren) elements.push(grandchildren);
966
+    }
967
+  });
968
+
969
+  return (elements.length>0 ? elements.flatten() : []);
970
+}
971
+
972
+Element.offsetSize = function (element, type) {
973
+  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
974
+}

+ 1122
- 0
res/effects.js
File diff suppressed because it is too large
View File


+ 28
- 0
res/lightbox.css View File

@@ -0,0 +1,28 @@
1
+#lightbox{	position: absolute;	left: 0; width: 100%; max-width: 100%; z-index: 100; text-align: center; line-height: 0;}
2
+#lightbox div { max-width: 100%; }
3
+#lightbox img{ width: auto; height: auto; max-width: 100%;}
4
+#lightbox a img{ border: none; }
5
+
6
+#outerImageContainer{ position: relative; background-color: #fff; width: 250px; height: 250px; margin: 0 auto; }
7
+#imageContainer{ padding: 10px; }
8
+
9
+#loading{ position: absolute; top: 40%; left: 0%; height: 25%; width: 100%; text-align: center; line-height: 0; }
10
+#hoverNav{ position: absolute; top: 0; left: 0; height: 100%; width: 100%; z-index: 10; }
11
+#imageContainer>#hoverNav{ left: 0;}
12
+#hoverNav a{ outline: none;}
13
+
14
+#prevLink, #nextLink{ width: 49%; height: 100%; background-image: url(data:image/gif;base64,AAAA); /* Trick IE into showing hover */ display: block; }
15
+#prevLink { left: 0; float: left;}
16
+#nextLink { right: 0; float: right;}
17
+#prevLink:hover, #prevLink:visited:hover { background: url(../images/prevlabel.gif) left 15% no-repeat; }
18
+#nextLink:hover, #nextLink:visited:hover { background: url(../images/nextlabel.gif) right 15% no-repeat; }
19
+
20
+#imageDataContainer{ font: 10px Verdana, Helvetica, sans-serif; background-color: #fff; margin: 0 auto; line-height: 1.4em; overflow: auto; width: 100%	; }
21
+
22
+#imageData{	padding:0 10px; color: #666; }
23
+#imageData #imageDetails{ width: 70%; float: left; text-align: left; }	
24
+#imageData #caption{ font-weight: bold;	}
25
+#imageData #numberDisplay{ display: block; clear: left; padding-bottom: 1.0em;	}			
26
+#imageData #bottomNavClose{ width: 66px; float: right;  padding-bottom: 0.7em; outline: none;}	 	
27
+
28
+#overlay{ position: absolute; top: 0; left: 0; z-index: 90; width: 100%; height: 500px; background-color: #000; }

+ 497
- 0
res/lightbox.js View File

@@ -0,0 +1,497 @@
1
+// -----------------------------------------------------------------------------------
2
+//
3
+//	Lightbox v2.04
4
+//	by Lokesh Dhakar - http://www.lokeshdhakar.com
5
+//	Last Modification: 2/9/08
6
+//
7
+//	For more information, visit:
8
+//	http://lokeshdhakar.com/projects/lightbox2/
9
+//
10
+//	Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
11
+//  	- Free for use in both personal and commercial projects
12
+//		- Attribution requires leaving author name, author link, and the license info intact.
13
+//	
14
+//  Thanks: Scott Upton(uptonic.com), Peter-Paul Koch(quirksmode.com), and Thomas Fuchs(mir.aculo.us) for ideas, libs, and snippets.
15
+//  		Artemy Tregubenko (arty.name) for cleanup and help in updating to latest ver of proto-aculous.
16
+//
17
+// -----------------------------------------------------------------------------------
18
+/*
19
+
20
+    Table of Contents
21
+    -----------------
22
+    Configuration
23
+
24
+    Lightbox Class Declaration
25
+    - initialize()
26
+    - updateImageList()
27
+    - start()
28
+    - changeImage()
29
+    - resizeImageContainer()
30
+    - showImage()
31
+    - updateDetails()
32
+    - updateNav()
33
+    - enableKeyboardNav()
34
+    - disableKeyboardNav()
35
+    - keyboardAction()
36
+    - preloadNeighborImages()
37
+    - end()
38
+    
39
+    Function Calls
40
+    - document.observe()
41
+   
42
+*/
43
+// -----------------------------------------------------------------------------------
44
+
45
+//
46
+//  Configurationl
47
+//
48
+LightboxOptions = Object.extend({
49
+    fileLoadingImage:        'images/loading.gif',     
50
+    fileBottomNavCloseImage: 'images/closelabel.gif',
51
+
52
+    overlayOpacity: 0.8,   // controls transparency of shadow overlay
53
+
54
+    animate: true,         // toggles resizing animations
55
+    resizeSpeed: 7,        // controls the speed of the image resizing animations (1=slowest and 10=fastest)
56
+
57
+    borderSize: 10,         //if you adjust the padding in the CSS, you will need to update this variable
58
+
59
+	// When grouping images this is used to write: Image # of #.
60
+	// Change it for non-english localization
61
+	labelImage: "Image",
62
+	labelOf: "of"
63
+}, window.LightboxOptions || {});
64
+
65
+// -----------------------------------------------------------------------------------
66
+
67
+var Lightbox = Class.create();
68
+
69
+Lightbox.prototype = {
70
+    imageArray: [],
71
+    activeImage: undefined,
72
+    
73
+    // initialize()
74
+    // Constructor runs on completion of the DOM loading. Calls updateImageList and then
75
+    // the function inserts html at the bottom of the page which is used to display the shadow 
76
+    // overlay and the image container.
77
+    //
78
+    initialize: function() {    
79
+        
80
+        this.updateImageList();
81
+        
82
+        this.keyboardAction = this.keyboardAction.bindAsEventListener(this);
83
+
84
+        if (LightboxOptions.resizeSpeed > 10) LightboxOptions.resizeSpeed = 10;
85
+        if (LightboxOptions.resizeSpeed < 1)  LightboxOptions.resizeSpeed = 1;
86
+
87
+	    this.resizeDuration = LightboxOptions.animate ? ((11 - LightboxOptions.resizeSpeed) * 0.15) : 0;
88
+	    this.overlayDuration = LightboxOptions.animate ? 0.2 : 0;  // shadow fade in/out duration
89
+
90
+        // When Lightbox starts it will resize itself from 250 by 250 to the current image dimension.
91
+        // If animations are turned off, it will be hidden as to prevent a flicker of a
92
+        // white 250 by 250 box.
93
+        var size = (LightboxOptions.animate ? 250 : 1) + 'px';
94
+        
95
+
96
+        // Code inserts html at the bottom of the page that looks similar to this:
97
+        //
98
+        //  <div id="overlay"></div>
99
+        //  <div id="lightbox">
100
+        //      <div id="outerImageContainer">
101
+        //          <div id="imageContainer">
102
+        //              <img id="lightboxImage">
103
+        //              <div style="" id="hoverNav">
104
+        //                  <a href="#" id="prevLink"></a>
105
+        //                  <a href="#" id="nextLink"></a>
106
+        //              </div>
107
+        //              <div id="loading">
108
+        //                  <a href="#" id="loadingLink">
109
+        //                      <img src="images/loading.gif">
110
+        //                  </a>
111
+        //              </div>
112
+        //          </div>
113
+        //      </div>
114
+        //      <div id="imageDataContainer">
115
+        //          <div id="imageData">
116
+        //              <div id="imageDetails">
117
+        //                  <span id="caption"></span>
118
+        //                  <span id="numberDisplay"></span>
119
+        //              </div>
120
+        //              <div id="bottomNav">
121
+        //                  <a href="#" id="bottomNavClose">
122
+        //                      <img src="images/close.gif">
123
+        //                  </a>
124
+        //              </div>
125
+        //          </div>
126
+        //      </div>
127
+        //  </div>
128
+
129
+
130
+        var objBody = $$('body')[0];
131
+
132
+		objBody.appendChild(Builder.node('div',{id:'overlay'}));
133
+	
134
+        objBody.appendChild(Builder.node('div',{id:'lightbox'}, [
135
+            Builder.node('div',{id:'outerImageContainer'}, 
136
+                Builder.node('div',{id:'imageContainer'}, [
137
+                    Builder.node('img',{id:'lightboxImage'}), 
138
+                    Builder.node('div',{id:'hoverNav'}, [
139
+                        Builder.node('a',{id:'prevLink', href: '#' }),
140
+                        Builder.node('a',{id:'nextLink', href: '#' })
141
+                    ]),
142
+                    Builder.node('div',{id:'loading'}, 
143
+                        Builder.node('a',{id:'loadingLink', href: '#' }, 
144
+                            Builder.node('img', {src: LightboxOptions.fileLoadingImage})
145
+                        )
146
+                    )
147
+                ])
148
+            ),
149
+            Builder.node('div', {id:'imageDataContainer'},
150
+                Builder.node('div',{id:'imageData'}, [
151
+                    Builder.node('div',{id:'imageDetails'}, [
152
+                        Builder.node('span',{id:'caption'}),
153
+                        Builder.node('span',{id:'numberDisplay'})
154
+                    ]),
155
+                    Builder.node('div',{id:'bottomNav'},
156
+                        Builder.node('a',{id:'bottomNavClose', href: '#' },
157
+                            Builder.node('img', { src: LightboxOptions.fileBottomNavCloseImage })
158
+                        )
159
+                    )
160
+                ])
161
+            )
162
+        ]));
163
+
164
+
165
+		$('overlay').hide().observe('click', (function() { this.end(); }).bind(this));
166
+		$('lightbox').hide().observe('click', (function(event) { if (event.element().id == 'lightbox') this.end(); }).bind(this));
167
+		$('outerImageContainer').setStyle({ width: size, height: size });
168
+		$('prevLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage - 1); }).bindAsEventListener(this));
169
+		$('nextLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage + 1); }).bindAsEventListener(this));
170
+		$('loadingLink').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
171
+		$('bottomNavClose').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
172
+
173
+        var th = this;
174
+        (function(){
175
+            var ids = 
176
+                'overlay lightbox outerImageContainer imageContainer lightboxImage hoverNav prevLink nextLink loading loadingLink ' + 
177
+                'imageDataContainer imageData imageDetails caption numberDisplay bottomNav bottomNavClose';   
178
+            $w(ids).each(function(id){ th[id] = $(id); });
179
+        }).defer();
180
+    },
181
+
182
+    //
183
+    // updateImageList()
184
+    // Loops through anchor tags looking for 'lightbox' references and applies onclick
185
+    // events to appropriate links. You can rerun after dynamically adding images w/ajax.
186
+    //
187
+    updateImageList: function() {   
188
+        this.updateImageList = Prototype.emptyFunction;
189
+
190
+        document.observe('click', (function(event){
191
+            var target = event.findElement('a[rel^=lightbox]') || event.findElement('area[rel^=lightbox]');
192
+            if (target) {
193
+                event.stop();
194
+                this.start(target);
195
+            }
196
+        }).bind(this));
197
+    },
198
+    
199
+    //
200
+    //  start()
201
+    //  Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
202
+    //
203
+    start: function(imageLink) {    
204
+
205
+        $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'hidden' });
206
+
207
+        // stretch overlay to fill page and fade in
208
+        var arrayPageSize = this.getPageSize();
209
+        $('overlay').setStyle({ width: arrayPageSize[0] + 'px', height: arrayPageSize[1] + 'px' });
210
+
211
+        new Effect.Appear(this.overlay, { duration: this.overlayDuration, from: 0.0, to: LightboxOptions.overlayOpacity });
212
+
213
+        this.imageArray = [];
214
+        var imageNum = 0;       
215
+
216
+        if ((imageLink.rel == 'lightbox')){
217
+            // if image is NOT part of a set, add single image to imageArray
218
+            this.imageArray.push([imageLink.href, imageLink.title]);         
219
+        } else {
220
+            // if image is part of a set..
221
+            this.imageArray = 
222
+                $$(imageLink.tagName + '[href][rel="' + imageLink.rel + '"]').
223
+                collect(function(anchor){ return [anchor.href, anchor.title]; }).
224
+                uniq();
225
+            
226
+            while (this.imageArray[imageNum][0] != imageLink.href) { imageNum++; }
227
+        }
228
+
229
+        // calculate top and left offset for the lightbox 
230
+        var arrayPageScroll = document.viewport.getScrollOffsets();
231
+        var lightboxTop = arrayPageScroll[1] + (document.viewport.getHeight() / 10);
232
+        var lightboxLeft = arrayPageScroll[0];
233
+        this.lightbox.setStyle({ top: lightboxTop + 'px', left: lightboxLeft + 'px' }).show();
234
+        
235
+        this.changeImage(imageNum);
236
+    },
237
+
238
+    //
239
+    //  changeImage()
240
+    //  Hide most elements and preload image in preparation for resizing image container.
241
+    //
242
+    changeImage: function(imageNum) {   
243
+        
244
+        this.activeImage = imageNum; // update global var
245
+
246
+        // hide elements during transition
247
+        if (LightboxOptions.animate) this.loading.show();
248
+        this.lightboxImage.hide();
249
+        this.hoverNav.hide();
250
+        this.prevLink.hide();
251
+        this.nextLink.hide();
252
+		// HACK: Opera9 does not currently support scriptaculous opacity and appear fx
253
+        this.imageDataContainer.setStyle({opacity: .0001});
254
+        this.numberDisplay.hide();      
255
+        
256
+        var imgPreloader = new Image();
257
+        
258
+        // once image is preloaded, resize image container
259
+
260
+
261
+        imgPreloader.onload = (function(){
262
+            this.lightboxImage.src = this.imageArray[this.activeImage][0];
263
+            this.resizeImageContainer(imgPreloader.width, imgPreloader.height);
264
+        }).bind(this);
265
+        imgPreloader.src = this.imageArray[this.activeImage][0];
266
+    },
267
+
268
+    //
269
+    //  resizeImageContainer()
270
+    //
271
+    resizeImageContainer: function(imgWidth, imgHeight) {
272
+
273
+        // get current width and height
274
+        var widthCurrent  = this.outerImageContainer.getWidth();
275
+        var heightCurrent = this.outerImageContainer.getHeight();
276
+
277
+        // get new width and height
278
+        var widthNew  = (imgWidth  + LightboxOptions.borderSize * 2);
279
+        var heightNew = (imgHeight + LightboxOptions.borderSize * 2);
280
+
281
+        // scalars based on change from old to new
282
+        var xScale = (widthNew  / widthCurrent)  * 100;
283
+        var yScale = (heightNew / heightCurrent) * 100;
284
+
285
+        // calculate size difference between new and old image, and resize if necessary
286
+        var wDiff = widthCurrent - widthNew;
287
+        var hDiff = heightCurrent - heightNew;
288
+
289
+        if (hDiff != 0) new Effect.Scale(this.outerImageContainer, yScale, {scaleX: false, duration: this.resizeDuration, queue: 'front'}); 
290
+        if (wDiff != 0) new Effect.Scale(this.outerImageContainer, xScale, {scaleY: false, duration: this.resizeDuration, delay: this.resizeDuration}); 
291
+
292
+        // if new and old image are same size and no scaling transition is necessary, 
293
+        // do a quick pause to prevent image flicker.
294
+        var timeout = 0;
295
+        if ((hDiff == 0) && (wDiff == 0)){
296
+            timeout = 100;
297
+            if (Prototype.Browser.IE) timeout = 250;   
298
+        }
299
+
300
+        (function(){
301
+            this.prevLink.setStyle({ height: imgHeight + 'px' });
302
+            this.nextLink.setStyle({ height: imgHeight + 'px' });
303
+            this.imageDataContainer.setStyle({ width: widthNew + 'px' });
304
+
305
+            this.showImage();
306
+        }).bind(this).delay(timeout / 1000);
307
+    },
308
+    
309
+    //
310
+    //  showImage()
311
+    //  Display image and begin preloading neighbors.
312
+    //
313
+    showImage: function(){
314
+        this.loading.hide();
315
+        new Effect.Appear(this.lightboxImage, { 
316
+            duration: this.resizeDuration, 
317
+            queue: 'end', 
318
+            afterFinish: (function(){ this.updateDetails(); }).bind(this) 
319
+        });
320
+        this.preloadNeighborImages();
321
+    },
322
+
323
+    //
324
+    //  updateDetails()
325
+    //  Display caption, image number, and bottom nav.
326
+    //
327
+    updateDetails: function() {
328
+    
329
+        // if caption is not null
330
+        if (this.imageArray[this.activeImage][1] != ""){
331
+            this.caption.update(this.imageArray[this.activeImage][1]).show();
332
+        }
333
+        
334
+        // if image is part of set display 'Image x of x' 
335
+        if (this.imageArray.length > 1){
336
+            this.numberDisplay.update( LightboxOptions.labelImage + ' ' + (this.activeImage + 1) + ' ' + LightboxOptions.labelOf + '  ' + this.imageArray.length).show();
337
+        }
338
+
339
+        new Effect.Parallel(
340
+            [ 
341
+                new Effect.SlideDown(this.imageDataContainer, { sync: true, duration: this.resizeDuration, from: 0.0, to: 1.0 }), 
342
+                new Effect.Appear(this.imageDataContainer, { sync: true, duration: this.resizeDuration }) 
343
+            ], 
344
+            { 
345
+                duration: this.resizeDuration, 
346
+                afterFinish: (function() {
347
+	                // update overlay size and update nav
348
+	                var arrayPageSize = this.getPageSize();
349
+	                this.overlay.setStyle({ height: arrayPageSize[1] + 'px' });
350
+	                this.updateNav();
351
+                }).bind(this)
352
+            } 
353
+        );
354
+    },
355
+
356
+    //
357
+    //  updateNav()
358
+    //  Display appropriate previous and next hover navigation.
359
+    //
360
+    updateNav: function() {
361
+
362
+        this.hoverNav.show();               
363
+
364
+        // if not first image in set, display prev image button
365
+        if (this.activeImage > 0) this.prevLink.show();
366
+
367
+        // if not last image in set, display next image button
368
+        if (this.activeImage < (this.imageArray.length - 1)) this.nextLink.show();
369
+        
370
+        this.enableKeyboardNav();
371
+    },
372
+
373
+    //
374
+    //  enableKeyboardNav()
375
+    //
376
+    enableKeyboardNav: function() {
377
+        document.observe('keydown', this.keyboardAction); 
378
+    },
379
+
380
+    //
381
+    //  disableKeyboardNav()
382
+    //
383
+    disableKeyboardNav: function() {
384
+        document.stopObserving('keydown', this.keyboardAction); 
385
+    },
386
+
387
+    //
388
+    //  keyboardAction()
389
+    //
390
+    keyboardAction: function(event) {
391
+        var keycode = event.keyCode;
392
+
393
+        var escapeKey;
394
+        if (event.DOM_VK_ESCAPE) {  // mozilla
395
+            escapeKey = event.DOM_VK_ESCAPE;
396
+        } else { // ie
397
+            escapeKey = 27;
398
+        }
399
+
400
+        var key = String.fromCharCode(keycode).toLowerCase();
401
+        
402
+        if (key.match(/x|o|c/) || (keycode == escapeKey)){ // close lightbox
403
+            this.end();
404
+        } else if ((key == 'p') || (keycode == 37)){ // display previous image
405
+            if (this.activeImage != 0){
406
+                this.disableKeyboardNav();
407
+                this.changeImage(this.activeImage - 1);
408
+            }
409
+        } else if ((key == 'n') || (keycode == 39)){ // display next image
410
+            if (this.activeImage != (this.imageArray.length - 1)){
411
+                this.disableKeyboardNav();
412
+                this.changeImage(this.activeImage + 1);
413
+            }
414
+        }
415
+    },
416
+
417
+    //
418
+    //  preloadNeighborImages()
419
+    //  Preload previous and next images.
420
+    //
421
+    preloadNeighborImages: function(){
422
+        var preloadNextImage, preloadPrevImage;
423
+        if (this.imageArray.length > this.activeImage + 1){
424
+            preloadNextImage = new Image();
425
+            preloadNextImage.src = this.imageArray[this.activeImage + 1][0];
426
+        }
427
+        if (this.activeImage > 0){
428
+            preloadPrevImage = new Image();
429
+            preloadPrevImage.src = this.imageArray[this.activeImage - 1][0];
430
+        }
431
+    
432
+    },
433
+
434
+    //
435
+    //  end()
436
+    //
437
+    end: function() {
438
+        this.disableKeyboardNav();
439
+        this.lightbox.hide();
440
+        new Effect.Fade(this.overlay, { duration: this.overlayDuration });
441
+        $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'visible' });
442
+    },
443
+
444
+    //
445
+    //  getPageSize()
446
+    //
447
+    getPageSize: function() {
448
+	        
449
+	     var xScroll, yScroll;
450
+		
451
+		if (window.innerHeight && window.scrollMaxY) {	
452
+			xScroll = window.innerWidth + window.scrollMaxX;
453
+			yScroll = window.innerHeight + window.scrollMaxY;
454
+		} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
455
+			xScroll = document.body.scrollWidth;
456
+			yScroll = document.body.scrollHeight;
457
+		} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
458
+			xScroll = document.body.offsetWidth;
459
+			yScroll = document.body.offsetHeight;
460
+		}
461
+		
462
+		var windowWidth, windowHeight;
463
+		
464
+		if (self.innerHeight) {	// all except Explorer
465
+			if(document.documentElement.clientWidth){
466
+				windowWidth = document.documentElement.clientWidth; 
467
+			} else {
468
+				windowWidth = self.innerWidth;
469
+			}
470
+			windowHeight = self.innerHeight;
471
+		} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
472
+			windowWidth = document.documentElement.clientWidth;
473
+			windowHeight = document.documentElement.clientHeight;
474
+		} else if (document.body) { // other Explorers
475
+			windowWidth = document.body.clientWidth;
476
+			windowHeight = document.body.clientHeight;
477
+		}	
478
+		
479
+		// for small pages with total height less then height of the viewport
480
+		if(yScroll < windowHeight){
481
+			pageHeight = windowHeight;
482
+		} else { 
483
+			pageHeight = yScroll;
484
+		}
485
+	
486
+		// for small pages with total width less then width of the viewport
487
+		if(xScroll < windowWidth){	
488
+			pageWidth = xScroll;		
489
+		} else {
490
+			pageWidth = windowWidth;
491
+		}
492
+
493
+		return [pageWidth,pageHeight];
494
+	}
495
+}
496
+
497
+document.observe('dom:loaded', function () { new Lightbox(); });

+ 4221
- 0
res/prototype.js
File diff suppressed because it is too large
View File


+ 58
- 0
res/scriptaculous.js View File

@@ -0,0 +1,58 @@
1
+// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+// 
5
+// Permission is hereby granted, free of charge, to any person obtaining
6
+// a copy of this software and associated documentation files (the
7
+// "Software"), to deal in the Software without restriction, including
8
+// without limitation the rights to use, copy, modify, merge, publish,
9
+// distribute, sublicense, and/or sell copies of the Software, and to
10
+// permit persons to whom the Software is furnished to do so, subject to
11
+// the following conditions:
12
+// 
13
+// The above copyright notice and this permission notice shall be
14
+// included in all copies or substantial portions of the Software.
15
+//
16
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+//
24
+// For details, see the script.aculo.us web site: http://script.aculo.us/
25
+
26
+var Scriptaculous = {
27
+  Version: '1.8.1',
28
+  require: function(libraryName) {
29
+    // inserting via DOM fails in Safari 2.0, so brute force approach
30
+    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
31
+  },
32
+  REQUIRED_PROTOTYPE: '1.6.0',
33
+  load: function() {
34
+    function convertVersionString(versionString){
35
+      var r = versionString.split('.');
36
+      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
37
+    }
38
+ 
39
+    if((typeof Prototype=='undefined') || 
40
+       (typeof Element == 'undefined') || 
41
+       (typeof Element.Methods=='undefined') ||
42
+       (convertVersionString(Prototype.Version) < 
43
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
44
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
45
+        Scriptaculous.REQUIRED_PROTOTYPE);
46
+    
47
+    $A(document.getElementsByTagName("script")).findAll( function(s) {
48
+      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
49
+    }).each( function(s) {
50
+      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
51
+      var includes = s.src.match(/\?.*load=([a-z,]*)/);
52
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
53
+       function(include) { Scriptaculous.require(path+include+'.js') });
54
+    });
55
+  }
56
+}
57
+
58
+Scriptaculous.load();

+ 275
- 0
res/slider.js View File

@@ -0,0 +1,275 @@
1
+// script.aculo.us slider.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs 
4
+//
5
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
6
+// For details, see the script.aculo.us web site: http://script.aculo.us/
7
+
8
+if (!Control) var Control = { };
9
+
10
+// options:
11
+//  axis: 'vertical', or 'horizontal' (default)
12
+//
13
+// callbacks:
14
+//  onChange(value)
15
+//  onSlide(value)
16
+Control.Slider = Class.create({
17
+  initialize: function(handle, track, options) {
18
+    var slider = this;
19
+    
20
+    if (Object.isArray(handle)) {
21
+      this.handles = handle.collect( function(e) { return $(e) });
22
+    } else {
23
+      this.handles = [$(handle)];
24
+    }
25
+    
26
+    this.track   = $(track);
27
+    this.options = options || { };
28
+
29
+    this.axis      = this.options.axis || 'horizontal';
30
+    this.increment = this.options.increment || 1;
31
+    this.step      = parseInt(this.options.step || '1');
32
+    this.range     = this.options.range || $R(0,1);
33
+    
34
+    this.value     = 0; // assure backwards compat
35
+    this.values    = this.handles.map( function() { return 0 });
36
+    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
37
+    this.options.startSpan = $(this.options.startSpan || null);
38
+    this.options.endSpan   = $(this.options.endSpan || null);
39
+
40
+    this.restricted = this.options.restricted || false;
41
+
42
+    this.maximum   = this.options.maximum || this.range.end;
43
+    this.minimum   = this.options.minimum || this.range.start;
44
+
45
+    // Will be used to align the handle onto the track, if necessary
46
+    this.alignX = parseInt(this.options.alignX || '0');
47
+    this.alignY = parseInt(this.options.alignY || '0');
48
+    
49
+    this.trackLength = this.maximumOffset() - this.minimumOffset();
50
+
51
+    this.handleLength = this.isVertical() ? 
52
+      (this.handles[0].offsetHeight != 0 ? 
53
+        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
54
+      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
55
+        this.handles[0].style.width.replace(/px$/,""));
56
+
57
+    this.active   = false;
58
+    this.dragging = false;
59
+    this.disabled = false;
60
+
61
+    if (this.options.disabled) this.setDisabled();
62
+
63
+    // Allowed values array
64
+    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
65
+    if (this.allowedValues) {
66
+      this.minimum = this.allowedValues.min();
67
+      this.maximum = this.allowedValues.max();
68
+    }
69
+
70
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
71
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
72
+    this.eventMouseMove = this.update.bindAsEventListener(this);
73
+
74
+    // Initialize handles in reverse (make sure first handle is active)
75
+    this.handles.each( function(h,i) {
76
+      i = slider.handles.length-1-i;
77
+      slider.setValue(parseFloat(
78
+        (Object.isArray(slider.options.sliderValue) ? 
79
+          slider.options.sliderValue[i] : slider.options.sliderValue) || 
80
+         slider.range.start), i);
81
+      h.makePositioned().observe("mousedown", slider.eventMouseDown);
82
+    });
83
+    
84
+    this.track.observe("mousedown", this.eventMouseDown);
85
+    document.observe("mouseup", this.eventMouseUp);
86
+    document.observe("mousemove", this.eventMouseMove);
87
+    
88
+    this.initialized = true;
89
+  },
90
+  dispose: function() {
91
+    var slider = this;    
92
+    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
93
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
94
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
95
+    this.handles.each( function(h) {
96
+      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
97
+    });
98
+  },
99
+  setDisabled: function(){
100
+    this.disabled = true;
101
+  },
102
+  setEnabled: function(){
103
+    this.disabled = false;
104
+  },  
105
+  getNearestValue: function(value){
106
+    if (this.allowedValues){
107
+      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
108
+      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
109
+      
110
+      var offset = Math.abs(this.allowedValues[0] - value);
111
+      var newValue = this.allowedValues[0];
112
+      this.allowedValues.each( function(v) {
113
+        var currentOffset = Math.abs(v - value);
114
+        if (currentOffset <= offset){
115
+          newValue = v;
116
+          offset = currentOffset;
117
+        } 
118
+      });
119
+      return newValue;
120
+    }
121
+    if (value > this.range.end) return this.range.end;
122
+    if (value < this.range.start) return this.range.start;
123
+    return value;
124
+  },
125
+  setValue: function(sliderValue, handleIdx){
126
+    if (!this.active) {
127
+      this.activeHandleIdx = handleIdx || 0;
128
+      this.activeHandle    = this.handles[this.activeHandleIdx];
129
+      this.updateStyles();
130
+    }
131
+    handleIdx = handleIdx || this.activeHandleIdx || 0;
132
+    if (this.initialized && this.restricted) {
133
+      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
134
+        sliderValue = this.values[handleIdx-1];
135
+      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
136
+        sliderValue = this.values[handleIdx+1];
137
+    }
138
+    sliderValue = this.getNearestValue(sliderValue);
139
+    this.values[handleIdx] = sliderValue;
140
+    this.value = this.values[0]; // assure backwards compat
141
+    
142
+    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
143
+      this.translateToPx(sliderValue);
144
+    
145
+    this.drawSpans();
146
+    if (!this.dragging || !this.event) this.updateFinished();
147
+  },
148
+  setValueBy: function(delta, handleIdx) {
149
+    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
150
+      handleIdx || this.activeHandleIdx || 0);
151
+  },
152
+  translateToPx: function(value) {
153
+    return Math.round(
154
+      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
155
+      (value - this.range.start)) + "px";
156
+  },
157
+  translateToValue: function(offset) {
158
+    return ((offset/(this.trackLength-this.handleLength) * 
159
+      (this.range.end-this.range.start)) + this.range.start);
160
+  },
161
+  getRange: function(range) {
162
+    var v = this.values.sortBy(Prototype.K); 
163
+    range = range || 0;
164
+    return $R(v[range],v[range+1]);
165
+  },
166
+  minimumOffset: function(){
167
+    return(this.isVertical() ? this.alignY : this.alignX);
168
+  },
169
+  maximumOffset: function(){
170
+    return(this.isVertical() ? 
171
+      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
172
+        this.track.style.height.replace(/px$/,"")) - this.alignY : 
173
+      (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
174
+        this.track.style.width.replace(/px$/,"")) - this.alignX);
175
+  },  
176
+  isVertical:  function(){
177
+    return (this.axis == 'vertical');
178
+  },
179
+  drawSpans: function() {
180
+    var slider = this;
181
+    if (this.spans)
182
+      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
183
+    if (this.options.startSpan)
184
+      this.setSpan(this.options.startSpan,
185
+        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
186
+    if (this.options.endSpan)
187
+      this.setSpan(this.options.endSpan, 
188
+        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
189
+  },
190
+  setSpan: function(span, range) {
191
+    if (this.isVertical()) {
192
+      span.style.top = this.translateToPx(range.start);
193
+      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
194
+    } else {
195
+      span.style.left = this.translateToPx(range.start);
196
+      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
197
+    }
198
+  },
199
+  updateStyles: function() {
200
+    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
201
+    Element.addClassName(this.activeHandle, 'selected');
202
+  },
203
+  startDrag: function(event) {
204
+    if (Event.isLeftClick(event)) {
205
+      if (!this.disabled){
206
+        this.active = true;
207
+        
208
+        var handle = Event.element(event);
209
+        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
210
+        var track = handle;
211
+        if (track==this.track) {
212
+          var offsets  = Position.cumulativeOffset(this.track); 
213
+          this.event = event;
214
+          this.setValue(this.translateToValue( 
215
+           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
216
+          ));
217
+          var offsets  = Position.cumulativeOffset(this.activeHandle);
218
+          this.offsetX = (pointer[0] - offsets[0]);
219
+          this.offsetY = (pointer[1] - offsets[1]);
220
+        } else {
221
+          // find the handle (prevents issues with Safari)
222
+          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
223
+            handle = handle.parentNode;
224
+            
225
+          if (this.handles.indexOf(handle)!=-1) {
226
+            this.activeHandle    = handle;
227
+            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
228
+            this.updateStyles();
229
+            
230
+            var offsets  = Position.cumulativeOffset(this.activeHandle);
231
+            this.offsetX = (pointer[0] - offsets[0]);
232
+            this.offsetY = (pointer[1] - offsets[1]);
233
+          }
234
+        }
235
+      }
236
+      Event.stop(event);
237
+    }
238
+  },
239
+  update: function(event) {
240
+   if (this.active) {
241
+      if (!this.dragging) this.dragging = true;
242
+      this.draw(event);
243
+      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
244
+      Event.stop(event);
245
+   }
246
+  },
247
+  draw: function(event) {
248
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
249
+    var offsets = Position.cumulativeOffset(this.track);
250
+    pointer[0] -= this.offsetX + offsets[0];
251
+    pointer[1] -= this.offsetY + offsets[1];
252
+    this.event = event;
253
+    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
254
+    if (this.initialized && this.options.onSlide)
255
+      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
256
+  },
257
+  endDrag: function(event) {
258
+    if (this.active && this.dragging) {
259
+      this.finishDrag(event, true);
260
+      Event.stop(event);
261
+    }
262
+    this.active = false;
263
+    this.dragging = false;
264
+  },  
265
+  finishDrag: function(event, success) {
266
+    this.active = false;
267
+    this.dragging = false;
268
+    this.updateFinished();
269
+  },
270
+  updateFinished: function() {
271
+    if (this.initialized && this.options.onChange) 
272
+      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
273
+    this.event = null;
274
+  }
275
+});

+ 55
- 0
res/sound.js View File

@@ -0,0 +1,55 @@
1
+// script.aculo.us sound.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//
5
+// Based on code created by Jules Gravinese (http://www.webveteran.com/)
6
+//
7
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
8
+// For details, see the script.aculo.us web site: http://script.aculo.us/
9
+
10
+Sound = {
11
+  tracks: {},
12
+  _enabled: true,
13
+  template:
14
+    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
15
+  enable: function(){
16
+    Sound._enabled = true;
17
+  },
18
+  disable: function(){
19
+    Sound._enabled = false;
20
+  },
21
+  play: function(url){
22
+    if(!Sound._enabled) return;
23
+    var options = Object.extend({
24
+      track: 'global', url: url, replace: false
25
+    }, arguments[1] || {});
26
+    
27
+    if(options.replace && this.tracks[options.track]) {
28
+      $R(0, this.tracks[options.track].id).each(function(id){
29
+        var sound = $('sound_'+options.track+'_'+id);
30
+        sound.Stop && sound.Stop();
31
+        sound.remove();
32
+      })
33
+      this.tracks[options.track] = null;
34
+    }
35
+      
36
+    if(!this.tracks[options.track])
37
+      this.tracks[options.track] = { id: 0 }
38
+    else
39
+      this.tracks[options.track].id++;
40
+      
41
+    options.id = this.tracks[options.track].id;
42
+    $$('body')[0].insert( 
43
+      Prototype.Browser.IE ? new Element('bgsound',{
44
+        id: 'sound_'+options.track+'_'+options.id,
45
+        src: options.url, loop: 1, autostart: true
46
+      }) : Sound.template.evaluate(options));
47
+  }
48
+};
49
+
50
+if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
51
+  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
52
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
53
+  else
54
+    Sound.play = function(){}
55
+}

+ 103
- 0
res/style.css View File

@@ -0,0 +1,103 @@
1
+div#search {
2
+ position: absolute;
3
+ top: 10px;
4
+ left: 20px;
5
+ width: 50%;
6
+ text-align: center;
7
+}
8
+
9
+div#search input {
10
+ width: 80%;
11
+}
12
+
13
+div#searchresults {
14
+ position: absolute;
15
+ border: 1px solid #999;
16
+
17
+ top: 50px;
18
+ left: 20px;
19
+ width: 50%;
20
+ bottom: 15px;
21
+ overflow: auto;
22
+}
23
+
24
+#previewtabs {
25
+ position: absolute;
26
+ left: 55%;
27
+ top: 29px;
28
+ margin: 0px;
29
+ padding: 0px;
30
+}
31
+
32
+#previewtabs li {
33
+ display: inline-block;
34
+ vertical-align: middle;
35
+}
36
+
37
+#previewtabs li a {
38
+ display: inline-block;
39
+ height: 16px;
40
+ background-color: #ccc;
41
+ border: 1px solid #999;
42
+ text-align: center;
43
+ vertical-align: middle;
44
+ padding: 2px 10px;
45
+ color: white;
46
+ text-decoration: none;
47
+ font-size: 12px;
48
+}
49
+
50
+#previewtabs li a.active {
51
+ color: black;
52
+}
53
+
54
+div#searchresults img {
55
+ cursor: pointer;
56
+}
57
+
58
+div.preview {
59
+ position: absolute;
60
+ top: 50px;
61
+ bottom: 15px;
62
+ right: 20px;
63
+ width: 45%;
64
+ padding: 5px;
65
+
66
+ border: 1px solid #999;
67
+ background-color: white;
68
+}
69
+
70
+div#preview {
71
+ text-align: center;
72
+}
73
+
74
+div.preview img {
75
+ max-width: 100%;
76
+ max-height: 100%;
77
+ cursor: pointer;
78
+}
79
+
80
+div.thumbnail {
81
+ float: left;
82
+ padding: 0px;
83
+ width: 80px;
84
+ height: 100px;
85
+ margin: 5px;
86
+}
87
+
88
+a img {
89
+ border: 0px;
90
+}
91
+
92
+th {
93
+ text-align: right;
94
+}
95
+
96
+div.preview h2 {
97
+ margin-top: 0px;
98
+}
99
+
100
+textarea {
101
+ width: 100%;
102
+ height: 35%;
103
+}

BIN
res/throbber.gif View File


+ 568
- 0
res/unittest.js View File

@@ -0,0 +1,568 @@
1
+// script.aculo.us unittest.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
5
+//           (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
6
+//
7
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
8
+// For details, see the script.aculo.us web site: http://script.aculo.us/
9
+
10
+// experimental, Firefox-only
11
+Event.simulateMouse = function(element, eventName) {
12
+  var options = Object.extend({
13
+    pointerX: 0,
14
+    pointerY: 0,
15
+    buttons:  0,
16
+    ctrlKey:  false,
17
+    altKey:   false,
18
+    shiftKey: false,
19
+    metaKey:  false
20
+  }, arguments[2] || {});
21
+  var oEvent = document.createEvent("MouseEvents");
22
+  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
23
+    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
24
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
25
+  
26
+  if(this.mark) Element.remove(this.mark);
27
+  this.mark = document.createElement('div');
28
+  this.mark.appendChild(document.createTextNode(" "));
29
+  document.body.appendChild(this.mark);
30
+  this.mark.style.position = 'absolute';
31
+  this.mark.style.top = options.pointerY + "px";
32
+  this.mark.style.left = options.pointerX + "px";
33
+  this.mark.style.width = "5px";
34
+  this.mark.style.height = "5px;";
35
+  this.mark.style.borderTop = "1px solid red;"
36
+  this.mark.style.borderLeft = "1px solid red;"
37
+  
38
+  if(this.step)
39
+    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
40
+  
41
+  $(element).dispatchEvent(oEvent);
42
+};
43
+
44
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
45
+// You need to downgrade to 1.0.4 for now to get this working
46
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
47
+Event.simulateKey = function(element, eventName) {
48
+  var options = Object.extend({
49
+    ctrlKey: false,
50
+    altKey: false,
51
+    shiftKey: false,
52
+    metaKey: false,
53
+    keyCode: 0,
54
+    charCode: 0
55
+  }, arguments[2] || {});
56
+
57
+  var oEvent = document.createEvent("KeyEvents");
58
+  oEvent.initKeyEvent(eventName, true, true, window, 
59
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
60
+    options.keyCode, options.charCode );
61
+  $(element).dispatchEvent(oEvent);
62
+};
63
+
64
+Event.simulateKeys = function(element, command) {
65
+  for(var i=0; i<command.length; i++) {
66
+    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
67
+  }
68
+};
69
+
70
+var Test = {}
71
+Test.Unit = {};
72
+
73
+// security exception workaround
74
+Test.Unit.inspect = Object.inspect;
75
+
76
+Test.Unit.Logger = Class.create();
77
+Test.Unit.Logger.prototype = {
78
+  initialize: function(log) {
79
+    this.log = $(log);
80
+    if (this.log) {
81
+      this._createLogTable();
82
+    }
83
+  },
84
+  start: function(testName) {
85
+    if (!this.log) return;
86
+    this.testName = testName;
87
+    this.lastLogLine = document.createElement('tr');
88
+    this.statusCell = document.createElement('td');
89
+    this.nameCell = document.createElement('td');
90
+    this.nameCell.className = "nameCell";
91
+    this.nameCell.appendChild(document.createTextNode(testName));
92
+    this.messageCell = document.createElement('td');
93
+    this.lastLogLine.appendChild(this.statusCell);
94
+    this.lastLogLine.appendChild(this.nameCell);
95
+    this.lastLogLine.appendChild(this.messageCell);
96
+    this.loglines.appendChild(this.lastLogLine);
97
+  },
98
+  finish: function(status, summary) {
99
+    if (!this.log) return;
100
+    this.lastLogLine.className = status;
101
+    this.statusCell.innerHTML = status;
102
+    this.messageCell.innerHTML = this._toHTML(summary);
103
+    this.addLinksToResults();
104
+  },
105
+  message: function(message) {
106
+    if (!this.log) return;
107
+    this.messageCell.innerHTML = this._toHTML(message);
108
+  },
109
+  summary: function(summary) {
110
+    if (!this.log) return;
111
+    this.logsummary.innerHTML = this._toHTML(summary);
112
+  },
113
+  _createLogTable: function() {
114
+    this.log.innerHTML =
115
+    '<div id="logsummary"></div>' +
116
+    '<table id="logtable">' +
117
+    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
118
+    '<tbody id="loglines"></tbody>' +
119
+    '</table>';
120
+    this.logsummary = $('logsummary')
121
+    this.loglines = $('loglines');
122
+  },
123
+  _toHTML: function(txt) {
124
+    return txt.escapeHTML().replace(/\n/g,"<br/>");
125
+  },
126
+  addLinksToResults: function(){ 
127
+    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
128
+      td.title = "Run only this test"
129
+      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
130
+    });
131
+    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
132
+      td.title = "Run all tests"
133
+      Event.observe(td, 'click', function(){ window.location.search = "";});
134
+    });
135
+  }
136
+}
137
+
138
+Test.Unit.Runner = Class.create();
139
+Test.Unit.Runner.prototype = {
140
+  initialize: function(testcases) {
141
+    this.options = Object.extend({
142
+      testLog: 'testlog'
143
+    }, arguments[1] || {});
144
+    this.options.resultsURL = this.parseResultsURLQueryParameter();
145
+    this.options.tests      = this.parseTestsQueryParameter();
146
+    if (this.options.testLog) {
147
+      this.options.testLog = $(this.options.testLog) || null;
148
+    }
149
+    if(this.options.tests) {
150
+      this.tests = [];
151
+      for(var i = 0; i < this.options.tests.length; i++) {
152
+        if(/^test/.test(this.options.tests[i])) {
153
+          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
154
+        }
155
+      }
156
+    } else {
157
+      if (this.options.test) {
158
+        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
159
+      } else {
160
+        this.tests = [];
161
+        for(var testcase in testcases) {
162
+          if(/^test/.test(testcase)) {
163
+            this.tests.push(
164
+               new Test.Unit.Testcase(
165
+                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
166
+                 testcases[testcase], testcases["setup"], testcases["teardown"]
167
+               ));
168
+          }
169
+        }
170
+      }
171
+    }
172
+    this.currentTest = 0;
173
+    this.logger = new Test.Unit.Logger(this.options.testLog);
174
+    setTimeout(this.runTests.bind(this), 1000);
175
+  },
176
+  parseResultsURLQueryParameter: function() {
177
+    return window.location.search.parseQuery()["resultsURL"];
178
+  },
179
+  parseTestsQueryParameter: function(){
180
+    if (window.location.search.parseQuery()["tests"]){
181
+        return window.location.search.parseQuery()["tests"].split(',');
182
+    };
183
+  },
184
+  // Returns:
185
+  //  "ERROR" if there was an error,
186
+  //  "FAILURE" if there was a failure, or
187
+  //  "SUCCESS" if there was neither
188
+  getResult: function() {
189
+    var hasFailure = false;
190
+    for(var i=0;i<this.tests.length;i++) {
191
+      if (this.tests[i].errors > 0) {
192
+        return "ERROR";
193
+      }
194
+      if (this.tests[i].failures > 0) {
195
+        hasFailure = true;
196
+      }
197
+    }
198
+    if (hasFailure) {
199
+      return "FAILURE";
200
+    } else {
201
+      return "SUCCESS";
202
+    }
203
+  },
204
+  postResults: function() {
205
+    if (this.options.resultsURL) {
206
+      new Ajax.Request(this.options.resultsURL, 
207
+        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
208
+    }
209
+  },
210
+  runTests: function() {
211
+    var test = this.tests[this.currentTest];
212
+    if (!test) {
213
+      // finished!
214
+      this.postResults();
215
+      this.logger.summary(this.summary());
216
+      return;
217
+    }
218
+    if(!test.isWaiting) {
219
+      this.logger.start(test.name);
220
+    }
221
+    test.run();
222
+    if(test.isWaiting) {
223
+      this.logger.message("Waiting for " + test.timeToWait + "ms");
224
+      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
225
+    } else {
226
+      this.logger.finish(test.status(), test.summary());
227
+      this.currentTest++;
228
+      // tail recursive, hopefully the browser will skip the stackframe
229
+      this.runTests();
230
+    }
231
+  },
232
+  summary: function() {
233
+    var assertions = 0;
234
+    var failures = 0;
235
+    var errors = 0;
236
+    var messages = [];
237
+    for(var i=0;i<this.tests.length;i++) {
238
+      assertions +=   this.tests[i].assertions;
239
+      failures   +=   this.tests[i].failures;
240
+      errors     +=   this.tests[i].errors;
241
+    }
242
+    return (
243
+      (this.options.context ? this.options.context + ': ': '') + 
244
+      this.tests.length + " tests, " + 
245
+      assertions + " assertions, " + 
246
+      failures   + " failures, " +
247
+      errors     + " errors");
248
+  }
249
+}
250
+
251
+Test.Unit.Assertions = Class.create();
252
+Test.Unit.Assertions.prototype = {
253
+  initialize: function() {
254
+    this.assertions = 0;
255
+    this.failures   = 0;
256
+    this.errors     = 0;
257
+    this.messages   = [];
258
+  },
259
+  summary: function() {
260
+    return (
261
+      this.assertions + " assertions, " + 
262
+      this.failures   + " failures, " +
263
+      this.errors     + " errors" + "\n" +
264
+      this.messages.join("\n"));
265
+  },
266
+  pass: function() {
267
+    this.assertions++;
268
+  },
269
+  fail: function(message) {
270
+    this.failures++;
271
+    this.messages.push("Failure: " + message);
272
+  },
273
+  info: function(message) {
274
+    this.messages.push("Info: " + message);
275
+  },
276
+  error: function(error) {
277
+    this.errors++;
278
+    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
279
+  },
280
+  status: function() {
281
+    if (this.failures > 0) return 'failed';
282
+    if (this.errors > 0) return 'error';
283
+    return 'passed';
284
+  },
285
+  assert: function(expression) {
286
+    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
287
+    try { expression ? this.pass() : 
288
+      this.fail(message); }
289
+    catch(e) { this.error(e); }
290
+  },
291
+  assertEqual: function(expected, actual) {
292
+    var message = arguments[2] || "assertEqual";
293
+    try { (expected == actual) ? this.pass() :
294
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
295
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
296
+    catch(e) { this.error(e); }
297
+  },
298
+  assertInspect: function(expected, actual) {
299
+    var message = arguments[2] || "assertInspect";
300
+    try { (expected == actual.inspect()) ? this.pass() :
301
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
302
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
303
+    catch(e) { this.error(e); }
304
+  },
305
+  assertEnumEqual: function(expected, actual) {
306
+    var message = arguments[2] || "assertEnumEqual";
307
+    try { $A(expected).length == $A(actual).length && 
308
+      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
309
+        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
310
+          ', actual ' + Test.Unit.inspect(actual)); }
311
+    catch(e) { this.error(e); }
312
+  },
313
+  assertNotEqual: function(expected, actual) {
314
+    var message = arguments[2] || "assertNotEqual";
315
+    try { (expected != actual) ? this.pass() : 
316
+      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
317
+    catch(e) { this.error(e); }
318
+  },
319
+  assertIdentical: function(expected, actual) { 
320
+    var message = arguments[2] || "assertIdentical"; 
321
+    try { (expected === actual) ? this.pass() : 
322
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
323
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
324
+    catch(e) { this.error(e); } 
325
+  },
326
+  assertNotIdentical: function(expected, actual) { 
327
+    var message = arguments[2] || "assertNotIdentical"; 
328
+    try { !(expected === actual) ? this.pass() : 
329
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
330
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
331
+    catch(e) { this.error(e); } 
332
+  },
333
+  assertNull: function(obj) {
334
+    var message = arguments[1] || 'assertNull'
335
+    try { (obj==null) ? this.pass() : 
336
+      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
337
+    catch(e) { this.error(e); }
338
+  },
339
+  assertMatch: function(expected, actual) {
340
+    var message = arguments[2] || 'assertMatch';
341
+    var regex = new RegExp(expected);
342
+    try { (regex.exec(actual)) ? this.pass() :
343
+      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
344
+    catch(e) { this.error(e); }
345
+  },
346
+  assertHidden: function(element) {
347
+    var message = arguments[1] || 'assertHidden';
348
+    this.assertEqual("none", element.style.display, message);
349
+  },
350
+  assertNotNull: function(object) {
351
+    var message = arguments[1] || 'assertNotNull';
352
+    this.assert(object != null, message);
353
+  },
354
+  assertType: function(expected, actual) {
355
+    var message = arguments[2] || 'assertType';
356
+    try { 
357
+      (actual.constructor == expected) ? this.pass() : 
358
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
359
+        '", actual "' + (actual.constructor) + '"'); }
360
+    catch(e) { this.error(e); }
361
+  },
362
+  assertNotOfType: function(expected, actual) {
363
+    var message = arguments[2] || 'assertNotOfType';
364
+    try { 
365
+      (actual.constructor != expected) ? this.pass() : 
366
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
367
+        '", actual "' + (actual.constructor) + '"'); }
368
+    catch(e) { this.error(e); }
369
+  },
370
+  assertInstanceOf: function(expected, actual) {
371
+    var message = arguments[2] || 'assertInstanceOf';
372
+    try { 
373
+      (actual instanceof expected) ? this.pass() : 
374
+      this.fail(message + ": object was not an instance of the expected type"); }
375
+    catch(e) { this.error(e); } 
376
+  },
377
+  assertNotInstanceOf: function(expected, actual) {
378
+    var message = arguments[2] || 'assertNotInstanceOf';
379
+    try { 
380
+      !(actual instanceof expected) ? this.pass() : 
381
+      this.fail(message + ": object was an instance of the not expected type"); }
382
+    catch(e) { this.error(e); } 
383
+  },
384
+  assertRespondsTo: function(method, obj) {
385
+    var message = arguments[2] || 'assertRespondsTo';
386
+    try {
387
+      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
388
+      this.fail(message + ": object doesn't respond to [" + method + "]"); }
389
+    catch(e) { this.error(e); }
390
+  },
391
+  assertReturnsTrue: function(method, obj) {
392
+    var message = arguments[2] || 'assertReturnsTrue';
393
+    try {
394
+      var m = obj[method];
395
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
396
+      m() ? this.pass() : 
397
+      this.fail(message + ": method returned false"); }
398
+    catch(e) { this.error(e); }
399
+  },
400
+  assertReturnsFalse: function(method, obj) {
401
+    var message = arguments[2] || 'assertReturnsFalse';
402
+    try {
403
+      var m = obj[method];
404
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
405
+      !m() ? this.pass() : 
406
+      this.fail(message + ": method returned true"); }
407
+    catch(e) { this.error(e); }
408
+  },
409
+  assertRaise: function(exceptionName, method) {
410
+    var message = arguments[2] || 'assertRaise';
411
+    try { 
412
+      method();
413
+      this.fail(message + ": exception expected but none was raised"); }
414
+    catch(e) {
415
+      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
416
+    }
417
+  },
418
+  assertElementsMatch: function() {
419
+    var expressions = $A(arguments), elements = $A(expressions.shift());
420
+    if (elements.length != expressions.length) {
421
+      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
422
+      return false;
423
+    }
424
+    elements.zip(expressions).all(function(pair, index) {
425
+      var element = $(pair.first()), expression = pair.last();
426
+      if (element.match(expression)) return true;
427
+      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
428
+    }.bind(this)) && this.pass();
429
+  },
430
+  assertElementMatches: function(element, expression) {
431
+    this.assertElementsMatch([element], expression);
432
+  },
433
+  benchmark: function(operation, iterations) {
434
+    var startAt = new Date();
435
+    (iterations || 1).times(operation);
436
+    var timeTaken = ((new Date())-startAt);
437
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
438
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
439
+    return timeTaken;
440
+  },
441
+  _isVisible: function(element) {
442
+    element = $(element);
443
+    if(!element.parentNode) return true;
444
+    this.assertNotNull(element);
445
+    if(element.style && Element.getStyle(element, 'display') == 'none')
446
+      return false;
447
+    
448
+    return this._isVisible(element.parentNode);
449
+  },
450
+  assertNotVisible: function(element) {
451
+    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
452
+  },
453
+  assertVisible: function(element) {
454
+    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
455
+  },
456
+  benchmark: function(operation, iterations) {
457
+    var startAt = new Date();
458
+    (iterations || 1).times(operation);
459
+    var timeTaken = ((new Date())-startAt);
460
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
461
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
462
+    return timeTaken;
463
+  }
464
+}
465
+
466
+Test.Unit.Testcase = Class.create();
467
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
468
+  initialize: function(name, test, setup, teardown) {
469
+    Test.Unit.Assertions.prototype.initialize.bind(this)();
470
+    this.name           = name;
471
+    
472
+    if(typeof test == 'string') {
473
+      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
474
+      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
475
+      this.test = function() {
476
+        eval('with(this){'+test+'}');
477
+      }
478
+    } else {
479
+      this.test = test || function() {};
480
+    }
481
+    
482
+    this.setup          = setup || function() {};
483
+    this.teardown       = teardown || function() {};
484
+    this.isWaiting      = false;
485
+    this.timeToWait     = 1000;
486
+  },
487
+  wait: function(time, nextPart) {
488
+    this.isWaiting = true;
489
+    this.test = nextPart;
490
+    this.timeToWait = time;
491
+  },
492
+  run: function() {
493
+    try {
494
+      try {
495
+        if (!this.isWaiting) this.setup.bind(this)();
496
+        this.isWaiting = false;
497
+        this.test.bind(this)();
498
+      } finally {
499
+        if(!this.isWaiting) {
500
+          this.teardown.bind(this)();
501
+        }
502
+      }
503
+    }
504
+    catch(e) { this.error(e); }
505
+  }
506
+});
507
+
508
+// *EXPERIMENTAL* BDD-style testing to please non-technical folk
509
+// This draws many ideas from RSpec http://rspec.rubyforge.org/
510
+
511
+Test.setupBDDExtensionMethods = function(){
512
+  var METHODMAP = {
513
+    shouldEqual:     'assertEqual',
514
+    shouldNotEqual:  'assertNotEqual',
515
+    shouldEqualEnum: 'assertEnumEqual',
516
+    shouldBeA:       'assertType',
517
+    shouldNotBeA:    'assertNotOfType',
518
+    shouldBeAn:      'assertType',
519
+    shouldNotBeAn:   'assertNotOfType',
520
+    shouldBeNull:    'assertNull',
521
+    shouldNotBeNull: 'assertNotNull',
522
+    
523
+    shouldBe:        'assertReturnsTrue',
524
+    shouldNotBe:     'assertReturnsFalse',
525
+    shouldRespondTo: 'assertRespondsTo'
526
+  };
527
+  var makeAssertion = function(assertion, args, object) { 
528
+   	this[assertion].apply(this,(args || []).concat([object]));
529
+  }
530
+  
531
+  Test.BDDMethods = {};   
532
+  $H(METHODMAP).each(function(pair) { 
533
+    Test.BDDMethods[pair.key] = function() { 
534
+       var args = $A(arguments); 
535
+       var scope = args.shift(); 
536
+       makeAssertion.apply(scope, [pair.value, args, this]); }; 
537
+  });
538
+  
539
+  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
540
+    function(p){ Object.extend(p, Test.BDDMethods) }
541
+  );
542
+}
543
+
544
+Test.context = function(name, spec, log){
545
+  Test.setupBDDExtensionMethods();
546
+  
547
+  var compiledSpec = {};
548
+  var titles = {};
549
+  for(specName in spec) {
550
+    switch(specName){
551
+      case "setup":
552
+      case "teardown":
553
+        compiledSpec[specName] = spec[specName];
554
+        break;
555
+      default:
556
+        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
557
+        var body = spec[specName].toString().split('\n').slice(1);
558
+        if(/^\{/.test(body[0])) body = body.slice(1);
559
+        body.pop();
560
+        body = body.map(function(statement){ 
561
+          return statement.strip()
562
+        });
563
+        compiledSpec[testName] = body.join('\n');
564
+        titles[testName] = specName;
565
+    }
566
+  }
567
+  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
568
+};

+ 20
- 0
search.php View File

@@ -0,0 +1,20 @@
1
+<?PHP
2
+
3
+ require_once('database.inc.php');
4
+
5
+ if (isset($_POST['query'])) {
6
+  $query = 'SELECT ocr_image,  MATCH(ocr_gocr, ocr_ocrad) AGAINST (\'' . mysql_real_escape_string($_POST['query']) . '\' IN BOOLEAN MODE) AS score FROM ocrresults WHERE MATCH(ocr_gocr, ocr_ocrad) AGAINST (\'' . mysql_real_escape_string($_POST['query']) . '\' IN BOOLEAN MODE) > 0 ORDER BY score DESC';
7
+ } else {
8
+  $query = 'SELECT ocr_image FROM ocrresults ORDER BY RAND()';
9
+ }
10
+
11
+ $sql = $query . ' LIMIT 0,42';
12
+ $res = mysql_query($sql) or die(mysql_error());
13
+
14
+ while ($row = mysql_fetch_assoc($res)) {
15
+  echo '<div class="thumbnail">';
16
+  echo '<img src="thumb.php?docs/' . $row['ocr_image'] . '" onmouseover="rollover(this);" onmouseout="rolloff(this);" onclick="click(this);">';
17
+  echo '</div>';
18
+ }
19
+
20
+?>

+ 114
- 0
thumb.php View File

@@ -0,0 +1,114 @@
1
+<?PHP
2
+
3
+/******************************************************************************\
4
+
5
+ thumb.php: Generate thumbnail image of specified file.
6
+ Copyright (C) 2004-2007 Chris 'MD87' Smith
7
+
8
+ This program is free software; you can redistribute it and/or
9
+ modify it under the terms of the GNU General Public License
10
+ as published by the Free Software Foundation; either version 2
11
+ of the License, or (at your option) any later version.
12
+
13
+ This program is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ GNU General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program; if not, write to the Free Software
20
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21
+
22
+|******************************************************************************|
23
+
24
+ Usage:
25
+ 
26
+  <img src="thumb.php?file_name.png"...>
27
+  
28
+  <img src="thumb.php?directory/file.jpg"...>
29
+  
30
+  <img src="thumb.php?http://www.example.com/file.jpg"...>
31
+
32
+
33
+\******************************************************************************/
34
+
35
+header('Cache-control: public');
36
+
37
+$image = $_SERVER['QUERY_STRING'];
38
+
39
+if (substr($image, 0, 6) == 'large:') {
40
+ define('LARGE', true);
41
+ $image = substr($image, 6);
42
+} else {
43
+ define('LARGE', false);
44
+}
45
+
46
+define('THUMB_WIDTH', LARGE ? 240 : 80);               // Maximum width for thumbnails
47
+define('THUMB_HEIGHT',LARGE ? 300 : 100);              // Maximum height for thumbnails
48
+define('THUMB_BACK','255,255,255');      // Background colour
49
+
50
+if (!file_exists($image)) {
51
+ 
52
+ /* TODO: Output error image. */
53
+ 
54
+ die();
55
+ 
56
+}
57
+
58
+if (file_exists('.thumbs/' . (LARGE ? 'large-' : '') . md5($image))) {
59
+ $mtime = filemtime('.thumbs/' . (LARGE ? 'large-' : '') . md5($image));
60
+ if ($mtime >= filemtime($image)) {
61
+  header('Content-type: image/jpeg');
62
+  readfile('.thumbs/' . (LARGE ? 'large-' : '') . md5($image));
63
+  exit;
64
+ }
65
+}
66
+
67
+/* TODO: Optimise. */
68
+
69
+
70
+if (($imi = @imagecreatefromjpeg($image)) === FALSE) {
71
+ 
72
+ if (($imi = @imagecreatefrompng($image)) === FALSE) {
73
+  
74
+  if (($imi = @imagecreatefromgif($image)) === FALSE) {
75
+   
76
+   /* TODO: Output error image. */
77
+   
78
+   die();
79
+   
80
+  }
81
+  
82
+ }
83
+ 
84
+}
85
+
86
+$width = imagesx($imi); $height = imagesy($imi);
87
+
88
+$Rwidth = (THUMB_WIDTH/$width);
89
+$Rheight = (THUMB_HEIGHT/$height);
90
+
91
+if ($Rwidth > $Rheight) { $ratio = $Rheight; } else { $ratio = $Rwidth; }
92
+
93
+if ($width > THUMB_WIDTH || $height > THUMB_HEIGHT) {
94
+ $Nwidth = $width * $ratio;
95
+ $Nheight = $height * $ratio;
96
+} else {
97
+ $Nheight = $height;
98
+ $Nwidth = $width;
99
+}
100
+
101
+$imo = imagecreatetruecolor(THUMB_WIDTH, LARGE ? $Nheight : THUMB_HEIGHT);
102
+
103
+$colour = explode(',',THUMB_BACK);
104
+
105
+imagefill($imo,1,1,imagecolorallocate($imo,$colour[0],$colour[1],$colour[2]));
106
+
107
+imagecopyresampled($imo,$imi,(THUMB_WIDTH-$Nwidth)/2 , LARGE ? 0 : (THUMB_HEIGHT-$Nheight)/2, 0, 0, $Nwidth, $Nheight, $width, $height);
108
+
109
+header('Content-type: image/jpeg');
110
+
111
+imagejpeg($imo, '.thumbs/' . (LARGE ? 'large-' : '') . md5($image), 100);
112
+imagejpeg($imo, false, 100);
113
+
114
+?>

Loading…
Cancel
Save