Merge branch 'master' of https://github.com/szmoore/MCTX3420 into users
authorJeremy Tan <[email protected]>
Sat, 26 Oct 2013 01:39:56 +0000 (09:39 +0800)
committerJeremy Tan <[email protected]>
Sat, 26 Oct 2013 01:39:56 +0000 (09:39 +0800)
Conflicts:
server/fastcgi.c
server/parameters

80 files changed:
.gitignore
irc/log
reports/final/teamwork - justin.docx [new file with mode: 0644]
server/actuator.c
server/actuator.h
server/actuators/Makefile
server/actuators/pregulator.c [new file with mode: 0644]
server/actuators/pregulator.h [new file with mode: 0644]
server/actuators/relays.c [new file with mode: 0644]
server/actuators/relays.h [new file with mode: 0644]
server/bbb_pin.c
server/control.h
server/fastcgi.c
server/fastcgi.h
server/filetest [new file with mode: 0644]
server/login.c
server/main.c
server/sensor.c
server/sensor.h
server/sensors/pressure.c
server/sensors/strain.c
server/sensors/strain.h
testing/CansWebInterface/To Be Ammended/UWA_generic3.jpg [new file with mode: 0644]
testing/CansWebInterface/To Be Ammended/mctx.gui2.js [new file with mode: 0644]
testing/CansWebInterface/To Be Ammended/style.css [new file with mode: 0644]
testing/CansWebInterface/WidgetCode/controlWidget [new file with mode: 0644]
testing/CansWebInterface/WidgetCode/statusPanel.html [new file with mode: 0644]
testing/CansWebInterface/WidgetCode/widgetTEMPLATE.txt [new file with mode: 0644]
testing/CansWebInterface/current/Graph1.png [new file with mode: 0644]
testing/CansWebInterface/current/Graph2.png [new file with mode: 0644]
testing/CansWebInterface/current/Graph3.png [new file with mode: 0644]
testing/CansWebInterface/current/Graph4.png [new file with mode: 0644]
testing/CansWebInterface/current/Graph5.png [new file with mode: 0644]
testing/CansWebInterface/current/Graph6.png [new file with mode: 0644]
testing/CansWebInterface/current/all.zip [new file with mode: 0644]
testing/CansWebInterface/current/dummy files to test download functionality.txt [new file with mode: 0644]
testing/CansWebInterface/current/nograph.png [new file with mode: 0644]
testing/CansWebInterface/current/pressure1 [new file with mode: 0644]
testing/CansWebInterface/current/pressure2 [new file with mode: 0644]
testing/CansWebInterface/current/strain1 [new file with mode: 0644]
testing/CansWebInterface/current/strain2 [new file with mode: 0644]
testing/CansWebInterface/current/strain3 [new file with mode: 0644]
testing/CansWebInterface/current/strain4 [new file with mode: 0644]
testing/CansWebInterface/dashboard.html [new file with mode: 0644]
testing/CansWebInterface/graph.html [new file with mode: 0644]
testing/CansWebInterface/help.html [new file with mode: 0644]
testing/CansWebInterface/index.html [new file with mode: 0644]
testing/CansWebInterface/index2.html [new file with mode: 0644]
testing/CansWebInterface/index3.html [new file with mode: 0644]
testing/CansWebInterface/static/OpenSans.eot [new file with mode: 0644]
testing/CansWebInterface/static/OpenSans.ttf [new file with mode: 0644]
testing/CansWebInterface/static/base64.js [new file with mode: 0644]
testing/CansWebInterface/static/excanvas.min.js [new file with mode: 0644]
testing/CansWebInterface/static/exp-menu.css [new file with mode: 0644]
testing/CansWebInterface/static/jquery-1.10.1.min.js [new file with mode: 0644]
testing/CansWebInterface/static/jquery.maphilight.min.js [new file with mode: 0644]
testing/CansWebInterface/static/mctx.control.js [new file with mode: 0644]
testing/CansWebInterface/static/mctx.gui.js [new file with mode: 0644]
testing/CansWebInterface/static/nav-menu.css [new file with mode: 0644]
testing/CansWebInterface/static/sbd4.png [new file with mode: 0644]
testing/CansWebInterface/static/style.css [new file with mode: 0644]
testing/CansWebInterface/static/uwacrest-text.png [new file with mode: 0644]
testing/CansWebInterface/template.html [new file with mode: 0644]
testing/CansWebInterface/test.html [new file with mode: 0644]
testing/MCTXWeb/public_html/control.html
testing/MCTXWeb/public_html/data.html
testing/MCTXWeb/public_html/graph.html
testing/MCTXWeb/public_html/help.html
testing/MCTXWeb/public_html/index.html
testing/MCTXWeb/public_html/login.html
testing/MCTXWeb/public_html/pintest.html
testing/MCTXWeb/public_html/static/html2canvas.js [new file with mode: 0644]
testing/MCTXWeb/public_html/static/mctx.control.js
testing/MCTXWeb/public_html/static/mctx.graph.js
testing/MCTXWeb/public_html/static/mctx.gui.js
testing/MCTXWeb/public_html/static/style.css
testing/MCTXWeb/public_html/stream.html [new file with mode: 0644]
testing/MCTXWeb/public_html/values.html [new file with mode: 0644]
testing/fastcgi-approach/post/post.c [new file with mode: 0644]
testing/pygui/pygui.py

index 770ed60..112c205 100644 (file)
@@ -2,6 +2,8 @@
 *.o
 *.exe
 *.dll
+*.a
+*.lib
 
 .DS_Store
 .DS_Store?
diff --git a/irc/log b/irc/log
index 6f60854..e4299cf 100644 (file)
--- a/irc/log
+++ b/irc/log
 21:10 < jtanx> working on the report the day before...
 21:10 < jtanx> what is this madness
 22:23 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Day changed Mon Oct 21 2013
+07:40 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+08:04 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+09:15 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+11:00 -!- jtanx [[email protected]] has quit [Ping timeout]
+11:03 -!- jtanx_ [[email protected]] has joined #mctxuwa_softdev
+11:03 -!- jtanx_ is now known as jtanx
+11:22 < jtanx> mctxserv[10834]: FATAL: Data_Save (data.c:80) - Error seeking to end of DataFile test/sensor_1 - File too large
+11:23 < jtanx> jeremy@pickle:~/git/MCTX3420/server/test$ du -sh *
+11:23 < jtanx> 0       actuator_0
+11:23 < jtanx> 2.0G    sensor_0
+11:23 < jtanx> 2.1G    sensor_1
+11:23 < jtanx> after running overnight
+11:23 < jtanx> hahaha
+11:36 < sam_moore> The 32G SD card might have been a good thing to buy...
+11:36 < sam_moore> Just in case
+11:36 < jtanx> I tried removing the line i made to fstab
+11:36 < jtanx> still didn't work
+11:36 < sam_moore> Damn
+11:36 < jtanx> was there anything important on it
+11:36 < jtanx> could just flash it with the internal memory
+11:37 < sam_moore> I'd be very hesitant to flash it to the internal memory if it doesn't boot
+11:37 < sam_moore> There may be something else wrong with it
+11:37 < jtanx> I mean
+11:37 < jtanx> there's a script
+11:37 < jtanx> to copy the internal memory to the sd card
+11:37 < sam_moore> Oh, that's cool
+11:37 < sam_moore> We can do that
+11:37 < jtanx> Okay
+11:37 < sam_moore> Since Ubuntu magically solved our image problems, we should use it
+11:38 < jtanx> yeah, that was weird
+11:38 < jtanx> just as long as there's nothing important on the sd card
+11:38 < sam_moore> Probably change the phrasing in the report from "We don't know why this fixed it" to "We fixed it through our 1337 haxor skills"
+11:38 < jtanx> >.>
+11:39 < jtanx> how did you set up usercake?
+11:39 < jtanx> like where's this db-settings.php that it mentions
+11:39 < jtanx> oh right
+11:39 < jtanx> never mind
+11:41 < sam_moore> It comes with an install directory that I removed
+11:41 < sam_moore> We should add that because we might want to modify the database structure
+11:41 < jtanx> yeah
+11:41 < sam_moore> Eg: It's a real pain that user permission levels are in a seperate table to everything else
+11:41 < sam_moore> And doesn't make sense...
+11:41 < jtanx> normalised database design
+11:41 < jtanx> :P
+11:42 < sam_moore> We can go with the "strcmp(user, "admin")" for now
+11:42 < sam_moore> Did you get the email about LEFT vs RIGHT can?
+11:42 < jtanx> yeah
+11:42 < sam_moore> I think I should ask
+11:42 < jtanx> pneumatics wants left
+11:43 < sam_moore> "Are you looking at the box from the FRONT or BACK"
+11:43 < jtanx> everyone else thought right
+11:43 < jtanx> hahahaah
+11:43 < sam_moore> "Also, which side is the FRONT"
+11:43 < jtanx> the case team got blasted this morning
+11:43 < sam_moore> Uh oh
+11:43 < jtanx> apparentl
+11:43 < jtanx> y
+11:43 < sam_moore> Then again, we all get blasted every week
+11:43 < jtanx> true
+11:43 < sam_moore> I think we manage to get the least blasting, but still
+11:43 < jtanx> oh yeah
+11:44 < jtanx> apparently the report IS individual
+11:44 < jtanx> per group
+11:44 < jtanx> I'm getting confused
+11:44 < sam_moore> Haha
+11:44 < jtanx> and it's due next monday???
+11:44 < sam_moore> -_-
+11:44 < jtanx> confusion over the due date
+11:44 < jtanx> adam apparently didn't know, and thought sparkplus would send out the date
+11:44 < jtanx> i don't know what sparkplus has got to do with this though
+11:45 < sam_moore> How long does it have to be?
+11:45 < jtanx> I have no idea
+11:45 < sam_moore> Sparkplus is for our peer evaluation
+11:45 < jtanx> yeah
+11:45 < jtanx> but the relation to report due date?
+11:45 < sam_moore> Pretty lazy if you ask me, and I don't care that they might read this (frankly I doubt it) to get the students to mark each other
+11:46 < jtanx> Yeah
+11:46 < jtanx> urgh
+11:46 < jtanx> getting 1045 (access denied) from mysql
+11:46 < jtanx> pretty sure I've got the pwd right
+11:46 < sam_moore> It might be what happens in the Real World (TM), but this is not some company, this is university, my grades should not depend on someone else
+11:47 < sam_moore> Try with `mysql` from the command line and check the password?
+11:50 < jtanx> herp derp
+11:50 < jtanx> no password
+11:53 < jtanx> now that I think about it, it's almost exactly like the django idea, except in php
+11:53 < sam_moore> Yep
+11:53 < jtanx> when you install it, is there any default login?
+11:54 < sam_moore> No; you register an account and that account becomes the admin
+11:54 < sam_moore> (of course you can also manually update the database)
+11:54 < jtanx> okay
+11:54 < sam_moore> You could change the install script to do that
+11:54 < jtanx> how are usernames and passwords stored in the db?
+11:54 < jtanx> crypt?
+11:54 < jtanx> (do you specify the algorithm?)
+11:55 < sam_moore> passwords are crypted with SHA6
+11:55 < sam_moore> The other stuff is plain text
+11:55 < sam_moore> UserCake default is SHA1 or something
+11:55 < jtanx> sha6?
+11:55 < sam_moore> I changed it to SHA6
+11:55 < jtanx> does it use something called pcks
+11:55 < jtanx> pcsk2
+11:55 < jtanx> pcks2*
+11:56 < sam_moore> Wait... by SHA6 I mean "SHA-Whatever-you-get-with-$6$-in-the-salt"
+11:56 < sam_moore> Which is actually SHA-512
+11:56 < sam_moore> Derp
+11:56 < jtanx> hmm
+11:57 < sam_moore> I couldn't find any references to security issues with doing it this way, but that doesn't mean there aren't any
+11:57 < sam_moore> However there's always going to be some security issues with anything we use
+11:57 < jtanx> yeah
+11:57 < jtanx> about the stuff in login.c
+11:57 < jtanx> you'll definitely want to change tat
+11:57 < jtanx> "SELECT password FROM %s WHERE user_name = \"%s\";", 
+11:58 < jtanx> sql injection right there
+11:58 < sam_moore> I know
+11:58 < sam_moore> That's why Login_Handler removes all non alpha-numeric characters from the user name
+11:58 < jtanx> the password?
+11:58 < sam_moore> The password isn't part of the MySQL query
+11:58 < sam_moore> Only the user name
+11:58 < jtanx> oh right
+11:58 < sam_moore> So the password can contain any characters
+11:59 < jtanx> yeah
+11:59 < jtanx> I know with sqlite you can use placeholders
+11:59 < jtanx> then you 'bind' stuff to those placeholders
+11:59 < sam_moore> I think you can with MySQL too
+11:59 < jtanx> probably
+11:59 < sam_moore> Feel free to change it if you want, I figured it was safe if username was already checked for bad characters
+12:00 < jtanx> yep
+12:03 < jtanx> Pneumatics and electronics wants to test the system this wednesday, starting from 10am
+12:03 < sam_moore> That provides a convenient reason to not work on ENSC1001
+12:03 < sam_moore> So I'll be there
+12:03 < jtanx> Hahaha
+12:08 < jtanx> nneded php5-gd
+12:08 < jtanx> these username/password restrictions are attrocious
+12:57 < sam_moore> The UserCake ones?
+12:57 < jtanx> yeah
+12:57 < sam_moore> Also, sorry I'll be late today
+12:57 < jtanx> that's ok
+12:58 < sam_moore> We're getting there...
+12:59 < sam_moore> The question is, do they want a finished project, or do they want a report on an unfinished project
+12:59 < jtanx> hahaha
+12:59 < sam_moore> Because we'll have to devote a lot of time to the report
+12:59 < jtanx> yes
+13:00 < sam_moore> We've looked into every layer of software from linux kernel drivers to databases and human computer interaction...
+13:01 < jtanx> o.o
+13:01 < sam_moore> I think we can conclude that they are all horrible hacks tied together with shoelaces
+13:01 < jtanx> about usercake
+13:02 < jtanx> is it just
+13:02 < jtanx> you have a set of pages
+13:02 < jtanx> and they're either accessible or not
+13:02 < jtanx> to the user?
+13:02 < sam_moore> Yep, and I know you're thinking we could put the API as one of those pages :P
+13:02 < jtanx> haha
+13:02 < sam_moore> But I think it's better to keep the API seperate from user management
+13:02 < jtanx> didn't think that
+13:02 < jtanx> but was just trying to understand usercake
+13:02 < sam_moore> Fair enough
+13:02 < jtanx> but since everything's in php
+13:03 < jtanx> we can do some templating
+13:03 < jtanx> instead of hacking it together with javascript
+13:03 < sam_moore> Sure
+13:03 < sam_moore> The main modification I wanted to make was a page that lets the admin upload a list of users and get rid of the registration page (or restrict it somehow)
+13:04 < jtanx> yeah
+13:04 < jtanx> but some things are better done in php
+13:04 < jtanx> like that whole 'load the sidebar in jaascript' is crap
+13:04 < jtanx> and the whole 'if not logged in, redirect the user with javascript' too
+13:04 < sam_moore> Feel free to add php to the main gui, it is probably a good choice
+13:05 < jtanx> okay
+13:05 < jtanx> once I figure out how stuff works
+13:05 < sam_moore> I'm just not experienced enough with this sort of web development stuff
+13:05 < jtanx> I can probably say it's a learing experience for everyone
+13:05 < sam_moore> Yes
+13:06 < sam_moore> I am pretty happy with how we've done this, even if it doesn't get finished
+13:06 < jtanx> Yeah, it's quite ok
+13:06 < sam_moore> I think we made good design choices
+13:06 < sam_moore> Probably the best one was when you convinced me to use FastCGI instead of writing a custom HTTP server :P
+13:07 < sam_moore> Or we'd be well and truly screwed when all the requirements for security and user management came along
+13:07 < jtanx> hahaha
+13:07 < jtanx> php
+13:07 < jtanx> custom webserver 
+13:07 < jtanx> well
+13:07 < jtanx> it might have worked
+13:08 < jtanx> call php5-cli with system
+13:08 < jtanx> hahaha
+13:08 < sam_moore> I could probably do something like that with enough time
+13:08 < sam_moore> But it wouldn't really be worth it
+13:14 < jtanx> oh right
+13:14 < jtanx> you changed usercake to use crypt 
+13:15 < jtanx> I'll change the password for the sd card image back to what we used before
+13:16 < jtanx> username will be ubuntu though
+13:18 < sam_moore> ok
+13:18 < jtanx> the internal memory still uses temppwd though
+13:20 < jtanx> if we use usercake we'd have to use php throughout the stuff anyway
+13:20 < jtanx> unless you want it to be publicly visible
+--- Log closed Mon Oct 21 14:11:57 2013
+--- Log opened Mon Oct 21 14:40:20 2013
+14:40 -!- sam_moore [[email protected]] has joined #mctxuwa_softdev
+14:40 -!- Irssi: #mctxuwa_softdev: Total of 2 nicks [0 ops, 0 halfops, 0 voices, 2 normal]
+14:40 -!- Irssi: Join to #mctxuwa_softdev was synced in 3 secs
+14:43 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+--- Log closed Mon Oct 21 14:49:35 2013
+--- Log opened Mon Oct 21 15:08:16 2013
+15:08 -!- sam_moore [[email protected]] has joined #mctxuwa_softdev
+15:08 -!- Irssi: #mctxuwa_softdev: Total of 1 nicks [0 ops, 0 halfops, 0 voices, 1 normal]
+15:08 -!- Irssi: Join to #mctxuwa_softdev was synced in 2 secs
+15:17 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+15:22 -!- jtanx_ [[email protected]] has joined #mctxuwa_softdev
+15:22 -!- jtanx_ is now known as jtanx
+15:22 -!- jtanx [[email protected]] has quit [EOF From client]
+18:17 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+18:36 -!- MctxBot [[email protected]] has quit [Ping timeout]
+22:14 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+22:57 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+--- Day changed Tue Oct 22 2013
+09:35 < sam_moore> So... looking at our system diagram, we have "mains electricity" going directly into the BeagleBone
+09:35 < sam_moore> That should probably be fixed
+09:38 < sam_moore> Dammit isn't there an svg version somewhere
+10:06 < sam_moore> Oliver said Adrian's goal was to "teach us a lesson"
+10:07 < sam_moore> If the goal is to turn us into cynical bastards, I was already a cynical bastard before this unit, so I fail to see the point
+10:07 < sam_moore> ... Talking to myself, excellent sign
+10:38 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+10:39 < sam_moore> Hi Jeremy
+10:40 < sam_moore> Are you OK with the section I listed for you?
+10:40 < jtanx> Sure, it seems okay
+10:41 < jtanx> This is going to be a busy week
+10:42 < sam_moore> I basically told my ENSC1001 team that if I have to choose between failing two units, I will choose to fail ENSC1001
+10:42 < sam_moore> I am now "that guy"
+10:43 < jtanx> Urgh :(
+10:44 < sam_moore> The requirements for the diary in terms of software are a bit ridiculous
+10:44 < sam_moore> In the 21st century
+10:44 < sam_moore> You'd think we could use git
+10:44 < sam_moore> As opposed to writing down source code in our diary
+10:44 < sam_moore> ... writing down code...
+10:44 < sam_moore> By hand
+10:44 < jtanx> Yeah, the requirements for this whole unit have been quite a stretch\
+10:44 < sam_moore> In a diary
+10:44 < sam_moore> What the hell???
+10:44 < jtanx> I asked during the meeting if he wanted a printed copy of the code :P
+10:45 < sam_moore> One idea I have had that would have been good
+10:45 < sam_moore> Is to always make 2 git commits whenever you work on something
+10:45 < sam_moore> One when you start
+10:45 < sam_moore> And one when you finish
+10:45 < jtanx> good idea
+10:46 < sam_moore> So, does he want a printed copy of the code?
+10:52 < jtanx> nah
+10:58 < sam_moore> Damn, that might have been fun to hand in
+10:58 < jtanx> We could do it just for shits
+10:58 < jtanx> print out the doxygen manual too
+10:58 < sam_moore> Hahaha
+10:58 < sam_moore> The doxygen manual is probably bigger than the printed source code
+10:59 < jtanx> Get a quote from uniprint 
+10:59 < jtanx> print and bind it
+10:59 < sam_moore> Sure
+10:59 < sam_moore> It won't help though
+10:59 < jtanx> yeah
+10:59 < sam_moore> The response to anything that looks like it was a large amount of work
+10:59 < sam_moore> is "you overcomplicated it!"
+10:59 < jtanx> ~.~
+11:02 < jtanx> okay, i'm leaving uni
+11:02 -!- jtanx [[email protected]] has quit ["http://www.mibbit.com ajax IRC Client"]
+11:31 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+11:37 < jtanx> well, I guess now's a good time than any to learn LaTeX
+11:39 < sam_moore> Haha
+11:39 < sam_moore> You don't have to learn LaTeX
+11:39 < sam_moore> It's just, for a 30 page report
+11:39 < sam_moore> If I am the one collating it
+11:39 < sam_moore> I want to use LaTeX
+11:39 < sam_moore> Hang on, I'll submit something in a minute, I got sidetracked looking at flow chart applications
+11:41 < jtanx> that's ok
+11:41 < jtanx> I've always wanted to learn latex anyway
+11:43 < sam_moore> If you're good at programming you can pick up latex pretty easily
+11:43 < jtanx> yeah, it doesn't look to bad
+11:43 < sam_moore> The scary bit is all the preamble stuff
+11:43 < sam_moore> Which you just copy/paste from examples unless you actually want to change something majorly
+11:43 < jtanx> haha
+11:43 < sam_moore> The rest is just like any markup language, except a bit more verbose
+11:44 < sam_moore> I have a nice template structure from my thesis we can use
+11:44 < jtanx> Cool
+11:57 < sam_moore> https://github.com/szmoore/MCTX3420/tree/report/reports/final
+11:58 < sam_moore> Essentially every chapter gets a single .tex file in the chapters directory
+11:58 < sam_moore> report.tex determines the formatting and stuff, so the chapter.tex files don't have to have anything other than content
+11:58 < jtanx> Okay
+11:59 < sam_moore> I'm not sure if it builds under windows easily
+11:59 < jtanx> well
+11:59 < jtanx> one of the reasons why i hesitated to use latex was because it's something like a few gb to isntall
+12:00 < jtanx> it should build under windows fine
+12:00 < sam_moore> Ah
+12:00 < sam_moore> I think it's worth it
+12:00 < jtanx> I have no disk space to install it on my laptop
+12:00 < sam_moore> For something of this scale... using a standard word processor...
+12:00 < sam_moore> Haha
+12:00 < jtanx> but it's installed on my server
+12:00 < sam_moore> Just worry about the content, I can make it compile either way
+12:00 < jtanx> yeah
+12:01 < sam_moore> Also, do you know any good flow chart stuff? Other than omnigraffle which is OSX only
+12:01 < jtanx> ms visio
+12:01 < jtanx> defacto standard
+12:02 < jtanx> never particularly liked it though
+12:03 < jtanx> it's free of msdnaa, but of course you need a windows computer...
+12:03 < sam_moore> Yeah, that will ruin my productivity
+12:04 < sam_moore> I do have windows, but rebooting is not fun, installing windows equivelants of all the software I use under linux is not fun, wine is probably broken, and running my windows operating system off the hard disk as a VM can be done, but gives temperamental results
+12:07 < jtanx> hahaha
+12:15 < sam_moore> Hmm, I have LibreOffice draw
+12:15 < sam_moore> Maybe that doesn't suck
+12:16 < jtanx> I hate using Libre/Open*
+12:16 < jtanx> never formats properly
+12:17 < sam_moore> Yeah... just trying to join two shapes together nicely is taking more than 3 seconds, screw that
+12:17 < jtanx> hahaha
+12:17 < sam_moore> I'll try this "Dia" thing
+12:18 < sam_moore> Oh dear
+12:18 < sam_moore> There's "Dia"
+12:18 < sam_moore> And "Diagramly"
+12:20 < jtanx> what about graphviz
+12:28 < sam_moore> giffly looks good
+12:28 < jtanx> Any particular name for our server software?
+12:28 < sam_moore> Oh god, it needs a name
+12:28 < jtanx> I've just been referring to it as 'Server API'
+12:28 < sam_moore> Yeah :S
+12:29 < sam_moore> I have a friend who didn't name their software and had to give a report on it and the only thing anyone commented on was its lack of name
+12:29 < sam_moore> So the next time...
+12:30 < jtanx> >.>
+12:30 < sam_moore> He said "This part of the software is 'sabertooth' and this part is 'timberwolf'"
+12:30 < sam_moore> And they liked it
+12:30 < jtanx> Hahaha
+12:30 < jtanx> what
+12:31 < sam_moore> ...
+12:31 < sam_moore> What's the greek god of disaster?
+12:31 < sam_moore> Or exploding pressurised vessels
+12:32 < sam_moore> Demeter, goddess of failure...
+12:33 < sam_moore> Just stick with Server API for now
+12:35 < sam_moore> I think I'll use gliffy even though it requires me to sell my soul to whatever company owns it
+12:35 < sam_moore> Can't be worse than facebook
+12:35 < jtanx> ._.
+12:35 < jtanx> fair enough
+12:36 < sam_moore> For a "vector graphics" editor, "dia" just looks horrible
+12:36 < sam_moore> The lines don't alias properly at all
+12:36 < jtanx> does it generate svg output
+12:37 < sam_moore> gliffy does and I'm not looking back :P
+12:37 < jtanx> hahaha
+12:42 < sam_moore> gliffy is really nice
+12:42 < jtanx> which flow chart are you doing
+12:42 < sam_moore> At the moment, the sensors thread
+12:43 < jtanx> Okay
+12:43 < jtanx> ahh
+12:43 < jtanx> gliffy would have been handy for uml crap I had to do
+12:46 < sam_moore> If you want to start with flow charts, try and do one for how ajax goes from the client and then data comes back through the labyrinth of FastCGI calls :P
+12:47 < sam_moore> I have a feeling they will consider flow charts to be "documentation" and not "report" though :S
+12:47 < jtanx> We can put it in the report
+12:47 < jtanx> then write about it
+12:47 < sam_moore> Haha
+12:47 < sam_moore> I actually have some results from experiments which they keep going on about in the marking key
+12:48 < sam_moore> I'm not sure what we can do for the results of tests from the GUI
+12:48 < jtanx> yeah
+12:48 < sam_moore> At one point I had a graph in flot that was plotting the time it took to update the graph in flot
+12:48 < sam_moore> But I never saved it
+12:48 < jtanx> o.O
+12:49 < sam_moore> Something that might be nice is a cachegrind profile
+12:49 < sam_moore> Whenever I do a report on software I try and profile it somehow
+12:49 < sam_moore> Anyway... the focus has shifted from "make the system work" to "write a report"
+12:49 < jtanx> Yeah
+12:50 < sam_moore> So we're pretty much just going to leave the system in an almost finished state, but since it would take someone a few weeks to learn it, they'll just start again...
+12:50 < sam_moore> and fail in all the same ways next year :(
+12:50 < jtanx> Hahahaha, of course
+13:48 < sam_moore> This flow chart stuff is really annoying
+13:48 < sam_moore> How much detail needs to be included
+13:48 < sam_moore> Argh
+13:48 < jtanx> I just made one in visio
+13:48 < jtanx> high level crap
+13:49 < jtanx> but still annoying to make
+13:49 < jtanx> I was hoping to make the flow chart
+13:49 < jtanx> then comment on that
+13:49 < jtanx> in the text
+13:49 < sam_moore> Ok
+13:50 < jtanx> Do you think something like this is ok?http://i.imgur.com/acNVXME.png
+13:52 < sam_moore> That looks good
+13:52 < sam_moore> It's completely inconsistent with what my sensor flow chart looks like visually though
+13:52 < jtanx> :S
+13:53 < sam_moore> Should we try make them all look the same, or not bother?
+13:53 < jtanx> Hmm
+13:53 < sam_moore> I'm not sure if this works; can you access it like this: https://www.gliffy.com/go/html5/5007621?app=1b5094b0-6042-11e2-bcfd-0800200c9a66
+13:53 < jtanx> I have to login
+13:54 < jtanx> presumably your login
+13:54 < sam_moore> Yeah, ok, I'll just put it under figures
+13:55 < sam_moore> https://github.com/szmoore/MCTX3420/blob/report/reports/final/figures/sensor_thread.png
+13:57 < jtanx> Nice
+14:00 < sam_moore> Whatever, don't worry about consistency, just make the charts
+14:00 < jtanx> If we get time, maybe then
+14:24 < sam_moore> We don't have time, I just spent 6 hours making one diagram :S
+14:24 < sam_moore> Ok, only 4.5 hours so far, but still
+14:24 < jtanx> :S
+14:24 < jtanx> I've spent what, 30 mins deciding how to write this one paragraph
+14:25 < jtanx> This is ridiculous
+14:25 < sam_moore> Were you there when Oliver said that Adrian's goal with this unit was to punish us?
+14:25 < jtanx> Hahaha
+14:25 < jtanx> Nah I wasn't there
+14:26 < sam_moore> The whole point is apparently to make us realise that we need a project manager
+14:26 < sam_moore> Something that I'm pretty sure I could work out for myself thankyou very much
+14:26 < jtanx> >.o
+14:29 < sam_moore> I've pushed some sort of report layout thing
+14:32 < sam_moore> It looks like most of the marks are on the "Approach" section
+14:32 < sam_moore> Which reminds me, I need to finish adding up my hours...
+14:33 < sam_moore> For cost calculation we could go "We have an average of X lines per day. According to James Trevelyn, the metric is 15 lines per day. Assuming this means 8 hours full time work at $150 an hour... we need this much money."
+14:34 < jtanx> Yeah
+16:09 < sam_moore> Ok, I'm starting to get the hang of this
+16:09 < sam_moore> If I just don't sleep for the next week
+16:09 < sam_moore> We'll be fine
+16:09 < jtanx> :S
+16:10 < sam_moore> Dammit are we still testing this system on Wednesday?
+16:10 < jtanx> I think so
+16:10 < jtanx> I'm trying to push out as much of this as possible today, but I'm not getting that far...
+16:10 < sam_moore> Ok, it should be fine, we just need some minor changes
+16:10 < sam_moore> Like... actually putting all the sensors/actuators in the software...
+16:10 < sam_moore> All the logic to control them is there though
+16:10 < jtanx> Yeah, maybe...
+16:11 < sam_moore> Don't worry about shit like the microphone in the graph page; the data can be recorded and downloaded, that's good enough for it
+16:11 < jtanx> Yep
+16:12 < jtanx> I don't particularly see the use of the microphone anyway
+16:12 < sam_moore> I don't know either
+16:13 < sam_moore> I realised that doing something like PID control with the current sensor/actuator logic would be a pain...
+16:13 < jtanx> Will we need pid control?
+16:14 < sam_moore> I don't think so
+16:14 < jtanx> I hope not
+16:14 < sam_moore> Well... not if the pressure regulator is good
+16:14 < jtanx> Here's to hoping
+16:14 < jtanx> (and if electronics gets their PWM-to-analogue thing going)
+16:15 < sam_moore> At least it's sort of possible to make PID control work, if you can identify the sensor somehow in Actuator_Loop and then just look at (sensor->current_data.value)/(sensor->points_read)
+16:15 < sam_moore> (Because I was lazy and reused current_data to store the sum of the points read before averaging, rather than the actual most recent point)
+16:16 < sam_moore> ergh, we'll fix that if it needs fixing anyway
+16:23 < sam_moore> Actually, if the goal is just to plot pressure vs strain, we don't care exactly what the pressure regulator is *supposed* to be... we just blindly increase it and then plot the strain vs an actual pressure sensor instead
+16:24 < jtanx> That's true
+16:26 < sam_moore> I wonder how harsh they will be on the "you must have designed the system completely before you implement it"
+16:26 < sam_moore> Do we have to determine what every line of code is going to be before we write them?
+16:27 < jtanx> Well there's no way that can be the case
+16:28 < jtanx> If that were the case i think we'd still be stuck trying to write the basics
+16:28 < sam_moore> At a high level our design is still exactly what is on that block diagram from ages ago
+16:28 < sam_moore> At a lower level... it's been through a few changes
+16:53 < sam_moore> ... I'm at 13 pages, but a lot of them are blank
+16:55 < jtanx> Ideally it should be ~5 pages/person since there's 6 people...
+16:55 < jtanx> How it actually turns out, well...
+16:57 < sam_moore> Well
+16:57 < sam_moore> One flow chart == 1 page...
+16:57 < sam_moore> I'm pretty sure I need to make at least 3 flow charts...
+16:57 < jtanx> Hahaha
+16:58 < sam_moore> Hmmm, could we hand in 29 pages of flow chart and 1 page of text...
+16:58 < sam_moore> The "teamwork" process could be a fun flowchart to make
+16:58 < sam_moore> Put lots of skull and crossbones on it
+16:58 < jtanx> :P
+16:59 < sam_moore> I'm pretty sure if you just count the figures I wanted to include that would make more than 5 pages
+16:59 < sam_moore> Perhaps this is achievable then
+17:00 < sam_moore> At least, getting 30 pages is achievable, not sure about meeting the marking criteria
+17:00 < jtanx> Yeah
+17:09 < jtanx> I'm at about 2 pages, and I've barely even started on explaining anything
+17:09 < jtanx> just reasoning why we chose what we chose
+17:28 < jtanx> "So Adrian just made an LMS announcement and the report is now due on Friday Week 13 (5pm), which is slightly more reasonable."
+17:28 < jtanx> (Justin just emailed)
+17:56 < sam_moore> I wonder if James' refreshing new GUI will be ready to show off by Tuesday
+17:56 < jtanx> Hehe
+17:57 < jtanx> Hopefully
+17:58 < sam_moore> LMS has two reports...
+17:58 < sam_moore> "Individual Report" and "Final Report"
+17:58 < jtanx> really
+17:58 < jtanx> why
+17:59 < sam_moore> Maybe the individual report is for the peer assessment mark?
+17:59 < jtanx> ah
+17:59 < jtanx> the marks on lms are depressing me
+18:00 < sam_moore> The progress report marks?
+18:00 < sam_moore> They are pretty depressing
+18:00 < jtanx> the quiz marks and soldering lab
+18:00 < sam_moore> Oh those
+18:00 < sam_moore> They are even more depressing
+18:01 < sam_moore> Wait, this maths doesn't make sense
+18:01 < sam_moore> The meetings are worth 52/1010
+18:01 < sam_moore> The tutorials are worth 808
+18:01 < sam_moore> The soldering video is 100
+18:01 < jtanx> it's lms being screwy
+18:01 < sam_moore> And our final report is worth 23?
+18:02 < jtanx> the quiz marks and tutorial is marked outof 100%
+18:02 < jtanx> I don't know what the others are marked out of
+18:02 < jtanx> (well tutes out of 4)
+18:11 -!- MctxBot [[email protected]] has quit [Ping timeout]
+19:16 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+20:20 -!- jtanx [[email protected]] has quit [Ping timeout]
+20:22 -!- jtanx_ [[email protected]] has joined #mctxuwa_softdev
+20:22 -!- jtanx_ is now known as jtanx
+21:14 < sam_moore> I just did a test that I probably should have done ages ago, but it confirms my assertion that C is much better (at least in terms of performance) for multithreaded stuff than python
+21:14 < jtanx> well yeah
+21:14 < jtanx> but then how much performance do you require
+21:14 < sam_moore> All the performance
+21:15 < jtanx> :P
+21:15 < sam_moore> At the fastest possible sampling rate, python is up around 0.01s in terms of the standard deviation of the distribution you get
+21:15 < sam_moore> So that's pretty bad
+21:18 < sam_moore> That's on a multicore processor actually
+21:18 < sam_moore> I guess I could repeat the test on the actual beaglebone, but I think the graph proves the point
+21:20 < sam_moore> ... Anyway, they said they wanted results of tests and things... so that's a test we can put in if we need to.
+21:21 < jtanx> Yeah
+21:24 < jtanx> https://github.com/niklasvh/html2canvas
+21:25 < jtanx> oh that's cool
+21:41 < sam_moore> Your section of the report is really good
+21:41 < jtanx> Really?
+21:41 < jtanx> I thought it was really wishy washy
+21:41 < sam_moore> Well it justifies the design choice pretty well
+21:42 < jtanx> Thanks
+21:42 < sam_moore> At one point people were seriously discussing using things other than HTTP
+21:43 < jtanx> What could we have used
+21:43 < jtanx> designed our own protocol?
+21:43 < sam_moore> Yeah, which would have sucked, that's what I had to explain
+21:44 < jtanx> Yeah
+21:44 < sam_moore> It was something along the lines of "why do we need to do all this complicated web stuff, can't we just have one device talking to another device over some kind of serial cable"
+21:44 < jtanx> I could imagine the heartache involved in trying to use some custom protocol
+21:44 < jtanx> hahaha
+21:44 < sam_moore> In the first place... you then have to build two custom devices as well as write a custom protocol...
+21:46 < jtanx> Yeah
+21:46 < sam_moore> We did need a raspberry pi or beaglebone, whether or not using an arduino as well for low level stuff would be better I'm not sure
+21:47 < jtanx> Hmm
+21:47 < sam_moore> I don't think it would have been worth the effort needed to get the RPi/BBB to talk to the arduino
+21:47 < jtanx> An arduino may have been better for all the gpio/adc stuff I guess
+21:47 < jtanx> but yeah
+21:47 < jtanx> the communication between the device and the arduino
+21:48 < sam_moore> Well you'd have a request coming in via HTTP, and then that would have to be sent over USB to the arduino to do the appropriate hardware control
+21:48 < sam_moore> Which would probably make it difficult to make the sensors/actuators independent of the client
+21:49 < jtanx> Well, there's also the option of i2c
+21:49 < jtanx> but yes
+21:49 < jtanx> an extra layer of communication
+21:49 < jtanx> :/
+21:49 < sam_moore> Either way, you have to store the data somehow... and you don't want it to be dependent on the client request... 
+21:50 < sam_moore> The more I think about it, the less I like it...
+21:51 < sam_moore> So I'm glad we went with the BeagleBone even though it's gpio/adc/pwm control is terrible
+21:51 < jtanx> Hahaha
+21:51 < jtanx> love/hate relationship with the bbb
+21:52 < sam_moore> You'd need to have interrupt handlers and some kind of synchronisation going on
+21:52 < sam_moore> You still need mutexes and conditionals with threads, but it's a lot easier
+21:53 < sam_moore> ... and we'd still need the multithreaded stuff anyway, because we'd still have sensors on the actual RPi/BBB to deal with
+21:53 < sam_moore> Yep, it would have been worse
+21:53 < jtanx> Yeah
+21:54 < sam_moore> We should definitely note this in the report somewhere
+21:55 < jtanx> Yeah
+21:55 < jtanx> Help justify why we agreed to the bbb
+21:55 < sam_moore> Well we did sort of agree to it because of that
+21:55 < sam_moore> It was partly "Hmm, we only have to write code for one device"
+21:56 < sam_moore> And partly because electronics wanted it to avoid having to build the extra hardware
+21:56 < sam_moore> And partly because if electronics wanted it then we wouldn't have to write our own BOM and order things :P
+21:56 < jtanx> :P
+22:18 < jtanx> MySQL Database BLARGH
+22:18 < jtanx> hehe
+22:21 < sam_moore> Do my thread flow charts make sense?
+22:22 < sam_moore> It's hard to know objectively
+22:22 < sam_moore> But I suspect looking at them for too long may cause the brain to shut down
+22:24 < sam_moore> Dammit, I can't remember if tomorrow is supposed to be some important assessment thing for ENSC1001 or not
+22:24 < jtanx> o.o
+22:24 < jtanx> I'm still trying to familiarise myself with the meaning of all the flow chart symbols
+22:24 < jtanx> but it seems okay
+22:24 < jtanx> don't you have to give a presentation or something for ensc1001
+22:25 < sam_moore> Yeah, but it's alright, that's next week
+22:25 < sam_moore> All the things are due next week
+22:26 < sam_moore> That's all that matters to me right now, because I want to sleep
+22:26 < jtanx> :S
+22:26 < sam_moore> Next Tuesday I will hate myself for sleeping this Tuesday
+22:26 < sam_moore> But Sam from next Tuesday is a jerk anyway
+22:26 < sam_moore> I never liked that guy
+22:26 < jtanx> Hey, sleep is important
+22:27 < jtanx> One thing about the flow charts, doesn't fatal just call exit(), so no deinitialisation happens
+22:27 < sam_moore> Um, sort of
+22:27 < sam_moore> But when the program exits, Cleanup gets called
+22:27 < sam_moore> Which calls the deinitialisation functions
+22:27 < jtanx> When did that get enabled
+22:28 < sam_moore> Cleanup has been there for ages
+22:28 < sam_moore> It's just been empty most of the time
+22:28 < jtanx> Ah
+22:28 < sam_moore> Although, I'm not sure if calling exit() is the best way to do it when you have multiple threads
+22:28 < jtanx> Meh
+22:28 < jtanx> when you have a fatal situation
+22:29 < sam_moore> Software isn't going to help?
+22:29 < jtanx> no
+22:29 < sam_moore> I was planning on having exit called with different error codes
+22:29 < jtanx> but what can you do with that
+22:30 < sam_moore> You can restart the server using run.sh
+22:30 < jtanx> but with the different error codes?
+22:30 < sam_moore> Or you can start a quick and dirty program that will make sure everything's deinitialised
+22:30 < jtanx> Ah
+22:30 < jtanx> I guess that could work
+22:30 < sam_moore> One error code for "really bad thing happened, we're going to die"
+22:30 < sam_moore> And one for "Something dumb happened, restart the server"
+22:31 < jtanx> About the graphs changing colours
+22:31 < jtanx> is it okay to just add a legend
+22:31 < sam_moore> It is OK to do whatever is the minimal work for the maximum reward
+22:31 < jtanx> hahaha
+22:31 < jtanx> well
+22:32 < jtanx> it was really easy to add the legend
+22:32 < sam_moore> Good, problem solved
+22:32 < jtanx> :P
+22:33 < jtanx> For the actuators
+22:33 < jtanx> did you remove that fatal call
+22:33 < jtanx> when the value was not safe?
+22:33 < sam_moore> I think I did
+22:33 < jtanx> Okay
+22:33 < sam_moore> It reports an error and doesn't set the actuator
+22:33 < sam_moore> And in theory it cancels the remaining steps
+22:33 < jtanx> That's good
+22:33 < sam_moore> Meaning the actuator just stays at the last safe value
+22:35 < sam_moore> Ah, it doesn't cancel the steps, but that can be easily changed
+22:35 < sam_moore> But currently it would just continually report the error until all the steps were finished
+22:36 < jtanx> either way's fine
+22:37 < jtanx> what's the default sample rate
+22:38 < sam_moore> It used to be 1s
+22:38 < jtanx> it's just that overnight it's generating about 2G of data
+22:38 < jtanx> which doesn't sound like 1s
+22:39 < sam_moore> Then it most likely isn't anymore
+22:39 < sam_moore> Nope, it's now 1e-4 seconds
+22:39 < sam_moore> Changed for the microphone test
+22:39 < jtanx> ahh
+22:40 < sam_moore> Should probably make the starting sample rate an argument to Sensor_Add
+22:40 < jtanx> I was wondering why it was consuming 30% cpu on idle
+22:40 < jtanx> ls
+22:40 < sam_moore> Ergh, if the microphone is running with everything else, it will generate a lot of data
+22:41 < jtanx> Yeah
+22:41 < sam_moore> Fortunately I added averaging
+22:41 < sam_moore> And apparently they just want an average from the microphone
+22:41 < sam_moore> Or a "level"
+22:41 < sam_moore> Or something
+22:41 < jtanx> oO
+22:42 < sam_moore> We'll just have the microphone sample at 1e-6 and have 1e6 averages per point
+22:42 < sam_moore> (ie: 1 point gets recorded every second)
+22:46 < sam_moore> Hmm, or you could make the microphone's ReadFn only succeed if the last pressure reading is higher than a certain value
+22:46 < sam_moore> But that's getting complicated
+22:49 < sam_moore> Good night anyway
+22:52 < jtanx> Okay
+22:53 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+23:01 -!- MctxBot [[email protected]] has quit [Ping timeout]
+23:55 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+--- Day changed Wed Oct 23 2013
+19:30 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+23:01 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Day changed Thu Oct 24 2013
+07:42 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+10:21 -!- MctxBot [[email protected]] has quit [Ping timeout]
+14:16 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+14:36 -!- Rowan [[email protected]] has joined #mctxuwa_softdev
+14:47 < jtanx> Hey
+15:45 -!- MctxBot [[email protected]] has quit [Connection reset by peer]
+15:47 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+17:27 -!- Rowan [[email protected]] has quit [EOF From client]
+17:51 < jtanx> huh
+17:51 < jtanx> it was surprisingly easy to add post support
+17:56 < sam_moore> That's cool
+17:56 < jtanx> you just fgets from stdin instead
+17:56 < jtanx> it's in the same format too
+17:56 < sam_moore> Excellent
+17:57 < jtanx> we probably do have to unescape html strings though
+17:57 < jtanx> like if someone had a comma in their password
+17:57 < sam_moore> If we had a progress report we could put "potential gaping security leak plugged"
+17:57 < jtanx> Hahaha
+17:57 < jtanx> but it still allows the get request version
+17:57 < sam_moore> Oh well
+17:57 < jtanx> but then that's your own fault
+17:57 < sam_moore> Yep
+17:59 < sam_moore> Doesn't look like the IRC channel will get a mention in the "communication" section of the report :(
+17:59 < sam_moore> Oh no wait, it does
+17:59 < sam_moore> Good
+17:59 < sam_moore> ... That "overall schedule"...
+17:59 < jtanx> Haha
+17:59 < sam_moore> I started it, and quickly realised it was a waste of time
+18:01 < sam_moore> Also, need to hate on the beaglebone less
+18:01 < sam_moore> Otherwise they might try and do something stupid like replace it next year
+18:01 < jtanx> That would be stupid
+18:01 < sam_moore> Yeah, it was a pain, but probably still one of the best things to use
+18:02 < sam_moore> RPi with dedicated ADC/DAC modules would probably have been best
+18:02 < sam_moore> Distributed system of arduinos.... noooo
+18:02 < jtanx> Yeah
+18:02 < jtanx> RPi with ADC/DAC would have been cool
+18:02 < jtanx> but only because there's more support for the RPi
+18:02 < sam_moore> Yes
+19:19 < jtanx> hmm
+19:20 < jtanx> maybe I should enable POST only for the login module?
+19:20 < jtanx> Right now, if POST and GET data is received, the GET data is discarded in place of the POST data
+19:28 < jtanx> Yeah, okay I only enabled it for login
+22:38 < jtanx> Hahahahaha
+22:39 < jtanx> I passed our code through ohloh - https://www.ohloh.net/p/MCTX3420
+22:39 < jtanx> Apparently it's work 3 years of effort, and is valued at $150k
+22:49 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Day changed Fri Oct 25 2013
+08:48 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+09:50 -!- MctxBot [[email protected]] has quit [Ping timeout]
+09:56 < sam_moore> Um... we didn't make our first commit in 200
+09:56 < sam_moore> * 2000
+09:56 < sam_moore> 10 developers???
+09:57 < sam_moore> There are 6 of us
+09:57 < sam_moore> And some people contributed about 2 lines of source code...
+09:57 < sam_moore> Mostly written in JavaScript
+09:57 < sam_moore> Really?
+09:57 < sam_moore> Is that counting the jQuery UI guff?
+09:58 < sam_moore> That's annoying, because the C part of the project is like 50% comment lines
+09:58 < sam_moore> And it says "low number of comments" based on the JavaScript :S
+09:59 < sam_moore> Still... this is really cool
+10:00 < jtanx> hahaha
+10:00 < jtanx> the 2000 commit was because we made commits on the bbb
+10:00 < sam_moore> ok...
+10:00 < jtanx> but it lacks an rtc 
+10:00 < sam_moore> Where do we get the extra 4 developers from?
+10:00 < sam_moore> "Debian" is one
+10:00 < jtanx> and for some reason the time didn't get updated
+10:01 < jtanx> umm
+10:01 < jtanx> hmm
+10:01 < jtanx> Ubuntu user?
+10:01 < sam_moore> Oh, "Callum" and "Callum-" are treated seperately -_-
+10:01 < jtanx> oh
+10:01 < sam_moore> That's still only 8
+10:02 < jtanx> the results are a bit skewed
+10:02 < jtanx> because a lot of the javascript is just from other libraries
+10:02 < jtanx> well at least i think so
+10:04 < sam_moore> Yeah
+10:05 < sam_moore> Is it easy to just pass a subset of the code through?
+10:08 < jtanx> Yep - you have to set what's ignored
+10:08 < jtanx> I've just set it to ignore all *.min.js files (essentially all external js files)
+10:08 < jtanx> But it won't get updated for a while
+10:09 < jtanx> I've got to go for ~ 2 hrs 
+10:10 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+10:12 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+12:46 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+13:42 -!- MctxBot [[email protected]] has quit [Ping timeout]
+20:44 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+21:20 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+21:36 -!- MctxBot [[email protected]] has quit [Ping timeout]
+23:39 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
diff --git a/reports/final/teamwork - justin.docx b/reports/final/teamwork - justin.docx
new file mode 100644 (file)
index 0000000..a27d002
Binary files /dev/null and b/reports/final/teamwork - justin.docx differ
index 1c105db..7473485 100644 (file)
@@ -35,7 +35,7 @@ int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn
        a->init = init; // Set init function
 
        a->sanity = sanity;
-
+       a->cleanup = cleanup;
        pthread_mutex_init(&(a->mutex), NULL);
 
        if (init != NULL)
@@ -51,16 +51,35 @@ int Actuator_Add(const char * name, int user_id, SetFn set, InitFn init, CleanFn
 
 
 /**
- * One off initialisation of *all* Actuators
+ * Initialisation of *all* Actuators
  */
-#include "actuators/ledtest.h"
-#include "actuators/filetest.h"
+#include "actuators/pregulator.h"
+#include "actuators/relays.h"
 void Actuator_Init()
 {
        //Actuator_Add("ledtest",0,  Ledtest_Set, NULL,NULL,NULL);
-       Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity, 0);
+       //Actuator_Add("filetest", 0, Filetest_Set, Filetest_Init, Filetest_Cleanup, Filetest_Sanity, 0);
+       Actuator_Add("pregulator", 0, Pregulator_Set, Pregulator_Init, Pregulator_Cleanup, Pregulator_Sanity, 0);
+       Actuator_Add("can_select", RELAY_CANSELECT, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 0);
+       Actuator_Add("can_enable", RELAY_CANENABLE, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 0);
+       Actuator_Add("main_pressure", RELAY_MAIN, Relay_Set, Relay_Init, Relay_Cleanup, Relay_Sanity, 1);
 }
 
+/**
+ * Deinitialise actuators
+ */
+void Actuator_Cleanup()
+{
+       for (int i = 0; i < g_num_actuators; ++i)
+       {
+               Actuator * a = g_actuators+i;
+               if (a->cleanup != NULL)
+                       a->cleanup(a->user_id);
+       }
+       g_num_actuators = 0;
+}
+
+
 /**
  * Sets the actuator to the desired mode. No checks are
  * done to see if setting to the desired mode will conflict with
@@ -143,8 +162,12 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg)
  */
 void Actuator_SetModeAll(ControlModes mode, void * arg)
 {
+       if (mode == CONTROL_START)
+               Actuator_Init();
        for (int i = 0; i < g_num_actuators; i++)
                Actuator_SetMode(&g_actuators[i], mode, arg);
+       if (mode == CONTROL_STOP)
+               Actuator_Cleanup();
 }
 
 /**
@@ -173,7 +196,7 @@ void * Actuator_Loop(void * arg)
                // Currently does discrete steps after specified time intervals
 
                struct timespec wait;
-               DOUBLE_TO_TIMEVAL(a->control.stepsize, &wait);
+               DOUBLE_TO_TIMEVAL(a->control.stepwait, &wait);
                while (!a->control_changed && a->control.steps > 0 && a->activated)
                {
                        clock_nanosleep(CLOCK_MONOTONIC, 0, &wait, NULL);
@@ -381,7 +404,7 @@ void Actuator_Handler(FCGIContext * context, char * params)
        
                ActuatorControl c = {0.0, 0.0, 0.0, 0}; // Need to set default values (since we don't require them all)
                // sscanf returns the number of fields successfully read...
-               int n = sscanf(set, "%lf,%lf,%lf,%d", &(c.start), &(c.stepwait), &(c.stepsize), &(c.steps)); // Set provided values in order
+               int n = sscanf(set, "%lf_%lf_%lf_%d", &(c.start), &(c.stepwait), &(c.stepsize), &(c.steps)); // Set provided values in order
                if (n != 4)
                {
                        //      If the user doesn't provide all 4 values, the Actuator will get set *once* using the first of the provided values
@@ -434,3 +457,9 @@ Actuator * Actuator_Identify(const char * name)
        }
        return NULL;
 }
+
+DataPoint Actuator_LastData(int id)
+{
+       Actuator * a = &(g_actuators[id]);
+       return a->last_setting;
+}
index f818526..46c2f44 100644 (file)
@@ -67,13 +67,14 @@ typedef struct
        /** Sanity check function **/
        SanityFn sanity;
        /** Cleanup function **/
-       CleanFn clean;
+       CleanFn cleanup;
        /** Last setting **/
        DataPoint last_setting;
        
 } Actuator;
 
 extern void Actuator_Init(); // One off initialisation of *all* Actuators
+extern void Actuator_Cleanup();
 
 extern void Actuator_SetModeAll(ControlModes mode, void *arg);
 extern void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg);
@@ -85,6 +86,7 @@ extern Actuator * Actuator_Identify(const char * str); // Identify a Sensor from
 
 extern void Actuator_Handler(FCGIContext *context, char * params); // Handle a FCGI request for Actuator control
 extern const char * Actuator_GetName(int id);
+extern DataPoint Actuator_LastData(int id);
 
 #endif //_ACTUATOR_H
 
index a2ba5dc..dfc247c 100644 (file)
@@ -3,7 +3,7 @@ CXX = gcc
 FLAGS = -std=c99 -Wall -pedantic -g -I../
 #-I/usr/include/opencv -I/usr/include/opencv2/highgui For OpenCV
 LIB = -lpthread
-OBJ = ledtest.o filetest.o
+OBJ = relays.o pregulator.o
 HEADERS = $(wildcard *.h)
 RM = rm -f
 
diff --git a/server/actuators/pregulator.c b/server/actuators/pregulator.c
new file mode 100644 (file)
index 0000000..3740729
--- /dev/null
@@ -0,0 +1,33 @@
+#include "pregulator.h"
+#include "../bbb_pin.h"
+
+#define PREGULATOR_PWM ECAP0
+#define PREGULATOR_PERIOD 500000
+//16666667
+
+/**
+ * Initiliase the pressure regulator
+ */
+bool Pregulator_Init(const char * name, int id)
+{
+       return PWM_Export(PREGULATOR_PWM) && PWM_Set(PREGULATOR_PWM, false, PREGULATOR_PERIOD, 0);
+}
+
+bool Pregulator_Cleanup(int id)
+{
+       if (!PWM_Set(PREGULATOR_PWM, false, PREGULATOR_PERIOD, 0))
+               return false;
+       PWM_Unexport(PREGULATOR_PWM);
+       return true;
+}
+
+bool Pregulator_Set(int id, double value)
+{
+       return PWM_Set(PREGULATOR_PWM, false, PREGULATOR_PERIOD, value*(PREGULATOR_PERIOD));
+}
+
+bool Pregulator_Sanity(int id, double value)
+{
+       return (value >= 0.0 && value <= 1.0);
+}
+
diff --git a/server/actuators/pregulator.h b/server/actuators/pregulator.h
new file mode 100644 (file)
index 0000000..c721140
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef _PREGULATOR
+
+#include "../common.h"
+
+extern bool Pregulator_Init(const char * name, int id);
+extern bool Pregulator_Cleanup(int id);
+extern bool Pregulator_Set(int id, double value);
+extern bool Pregulator_Sanity(int id, double value);
+
+
+#endif //_PREGULATOR
diff --git a/server/actuators/relays.c b/server/actuators/relays.c
new file mode 100644 (file)
index 0000000..dfa3d74
--- /dev/null
@@ -0,0 +1,48 @@
+#include "relays.h"
+#include "../log.h"
+
+#include "../bbb_pin.h"
+
+
+static int GetGPIO(int id)
+{
+       switch (id)
+       {
+               case RELAY_CANSELECT:
+                       return 14;
+               case RELAY_CANENABLE:
+                       return 115;
+               case RELAY_MAIN:
+                       return 112;
+       }
+       Fatal("Unknown id %d", id);
+       return 0;
+}
+
+bool Relay_Init(const char * name, int id)
+{
+       if (!GPIO_Export(GetGPIO(id)))
+               return false;
+       return GPIO_Set(GetGPIO(id), false);
+}
+
+bool Relay_Cleanup(int id)
+{
+       bool err = GPIO_Set(GetGPIO(id), false);
+       GPIO_Unexport(GetGPIO(id));
+       return err;
+}
+
+bool Relay_Set(int id, double value)
+{
+       bool set = (bool)value;
+       return GPIO_Set(GetGPIO(id), set);
+}
+
+bool Relay_Sanity(int id, double value)
+{
+       //bool set = (bool)value;
+       //TODO: Make a more sane sanity check
+       return true;
+       
+}
diff --git a/server/actuators/relays.h b/server/actuators/relays.h
new file mode 100644 (file)
index 0000000..dcffbf9
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef _RELAY_H
+#define _RELAY_H
+
+#include "../common.h"
+
+typedef enum
+{
+       RELAY_CANSELECT,
+       RELAY_CANENABLE,
+       RELAY_MAIN
+} RelayID;
+
+extern bool Relay_Init(const char * name, int id);
+extern bool Relay_Set(int id, double value);
+extern bool Relay_Sanity(int id, double value);
+extern bool Relay_Cleanup(int id);
+
+#endif //_RELAY_H
index 2ffc9e5..bd31365 100644 (file)
@@ -501,7 +501,8 @@ bool ADC_Read(int id, int *value)
 
        if (pread(g_adc[id].fd_value, adc_str, ADC_DIGITS-1, 0) == -1)
        {
-               AbortBool("ADC %d read failed: %s", id, strerror(errno));
+               //AbortBool("ADC %d read failed: %s", id, strerror(errno));
+               return false;
        }
 
        *value = strtol(adc_str, NULL, 10);
index 7081c3f..0dfdc47 100644 (file)
@@ -18,6 +18,8 @@ typedef enum ControlModes {
 /** The same as INVALID_CHARACTERS, except escaped for use in JSON strings **/
 #define INVALID_CHARACTERS_JSON "\\\"*/:<>?\\\\|. "
 
+#define NOAUTH_USERNAME "_anonymous_noauth"
+
 extern void Control_Handler(FCGIContext *context, char *params);
 extern const char* Control_SetMode(ControlModes desired_mode, void * arg);
 extern ControlModes Control_GetMode();
index af92efd..94742bd 100644 (file)
@@ -11,6 +11,7 @@
 #include <stdarg.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <ctype.h>
 
 #include "common.h"
 #include "sensor.h"
@@ -46,8 +47,7 @@ static void IdentifyHandler(FCGIContext *context, char *params)
        FCGI_BeginJSON(context, STATUS_OK);
        FCGI_JSONPair("description", "MCTX3420 Server API (2013)");
        FCGI_JSONPair("build_date", __DATE__ " " __TIME__);
-       struct timespec t;
-       t.tv_sec = 0; t.tv_nsec = 0;
+       struct timespec t = {0};
        clock_getres(CLOCK_MONOTONIC, &t);
        FCGI_JSONDouble("clock_getres", TIMEVAL_TO_DOUBLE(t));
        FCGI_JSONLong("api_version", API_VERSION);
@@ -65,7 +65,9 @@ static void IdentifyHandler(FCGIContext *context, char *params)
                        if (i > 0) {
                                FCGI_JSONValue(",\n\t\t");
                        }
-                       FCGI_JSONValue("\"%d\" : \"%s\"", i, Sensor_GetName(i)); 
+                       DataPoint d = Sensor_LastData(i);
+                       FCGI_JSONValue("\"%d\" : {\"name\" : \"%s\", \"value\" : [%f,%f] }", 
+                               i, Sensor_GetName(i), d.time_stamp, d.value); 
                }
                FCGI_JSONValue("\n\t}");
        }
@@ -76,7 +78,9 @@ static void IdentifyHandler(FCGIContext *context, char *params)
                        if (i > 0) {
                                FCGI_JSONValue(",\n\t\t");
                        }
-                       FCGI_JSONValue("\"%d\" : \"%s\"", i, Actuator_GetName(i)); 
+
+                       DataPoint d = Actuator_LastData(i);
+                       FCGI_JSONValue("\"%d\" : {\"name\" : \"%s\", \"value\" : [%f, %f] }", i, Actuator_GetName(i), d.time_stamp, d.value); 
                }
                FCGI_JSONValue("\n\t}");
        }
@@ -534,6 +538,40 @@ char *FCGI_EscapeText(char *buf)
        return buf;
 }
 
+/**
+ * Unescapes a URL encoded string in-place. The string
+ * must be NULL terminated.
+ * (e.g this%2d+string --> this- string)
+ * @param buf The buffer to decode. Will be modified in-place.
+ * @return The same buffer.
+ */
+char *FCGI_URLDecode(char *buf)
+{
+       char *head = buf, *tail = buf;
+       char hex[3] = {0};
+
+       while (*tail) {
+               if (*tail == '%') { //%hh hex to char
+                       tail++;
+                       if (isxdigit(*tail) && isxdigit(*(tail+1))) {
+                               hex[0] = *tail++;
+                               hex[1] = *tail++;
+                               *head++ = (char)strtol(hex, NULL, 16);
+                       } else { //Not valid format; keep original
+                               head++;
+                       }
+               } else if (*tail == '+') { //Plus to space
+                       tail++;
+                       *head++ = ' ';
+               } else { //Anything else
+                       *head++ = *tail++;
+               }
+       }
+       *head = 0; //NULL-terminate at new end point
+
+       return buf;
+}
+
 /**
  * Main FCGI request loop that receives/responds to client requests.
  * @param data Reserved.
@@ -552,7 +590,11 @@ void * FCGI_RequestLoop (void *data)
                
                //strncpy doesn't zero-truncate properly
                snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL"));
+               
+               //Get the GET query string
                snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING"));
+               //URL decode the parameters
+               FCGI_URLDecode(params);
 
                FCGI_GetControlCookie(context.received_key);
                Log(LOGDEBUG, "Got request #%d - Module %s, params %s", context.response_number, module, params);
@@ -589,26 +631,38 @@ void * FCGI_RequestLoop (void *data)
                context.current_module = module;
                context.response_number++;
                
-               if (module_handler) 
-               {
-                       if (g_options.auth_method != AUTH_NONE && module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler)
-                       //if (false) // Testing
-                       {
+               if (module_handler) {
+                       if (module_handler == IdentifyHandler) {
+                               FCGI_EscapeText(params);
+                       } else if (module_handler != Login_Handler) {
                                if (!FCGI_HasControl(&context))
                                {
-                                       FCGI_RejectJSON(&context, "Please login. Invalid control key.");
-                                       continue;       
+                                       if (g_options.auth_method == AUTH_NONE) {       //:(
+                                               Log(LOGWARN, "Locking control (no auth!)");
+                                               FCGI_LockControl(&context, NOAUTH_USERNAME, USER_ADMIN);
+                                               FCGI_SendControlCookie(&context, true);
+                                       }
+                                       else {
+                                               FCGI_RejectJSON(&context, "Please login. Invalid control key.");
+                                               continue;
+                                       }
                                }
-
+                               
                                //Escape all special characters.
                                //Don't escape for login (password may have special chars?)
                                FCGI_EscapeText(params);
+                       } else { //Only for Login handler.
+                               //If GET data is empty, use POST instead.
+                               if (*params == '\0') {
+                                       Log(LOGDEBUG, "Using POST!");
+                                       fgets(params, BUFSIZ, stdin); 
+                                       FCGI_URLDecode(params);
+                               }
                        }
 
                        module_handler(&context, params);
                } 
-               else 
-               {
+               else {
                        FCGI_RejectJSON(&context, "Unhandled module");
                }
        }
index 5826ece..a417a43 100644 (file)
@@ -85,6 +85,7 @@ extern void FCGI_JSONKey(const char *key);
 extern void FCGI_PrintRaw(const char *format, ...);
 extern void FCGI_EndJSON();
 extern void FCGI_RejectJSONEx(FCGIContext *context, StatusCodes status, const char *description);
+extern char *FCGI_URLDecode(char *buf);
 extern char *FCGI_EscapeText(char *buf);
 extern void *FCGI_RequestLoop (void *data);
 
diff --git a/server/filetest b/server/filetest
new file mode 100644 (file)
index 0000000..945da8f
--- /dev/null
@@ -0,0 +1 @@
+0.000000
index 8ca2cab..3445c04 100644 (file)
@@ -286,7 +286,10 @@ void Logout_Handler(FCGIContext * context, char * params)
 /**
  * Handle a Login Request
  * @param context - The context
- * @param params - Parameter string, should contain username and password
+ * @param params - Parameter string, should contain username and password.
+ *                                NOTE: Care should be taken when using params, as it is
+ *                                completely unescaped. Do not log or use it without
+ *                 suitable escaping.
  */
 void Login_Handler(FCGIContext * context, char * params)
 {
@@ -328,7 +331,7 @@ void Login_Handler(FCGIContext * context, char * params)
 
                case AUTH_LDAP:
                {
-                       if (strlen(pass) <= 0)
+                       if (*pass == '\0')
                        {
                                FCGI_RejectJSON(context, "No password supplied.");
                                return;
index 38fc9f0..2ca053e 100644 (file)
@@ -160,7 +160,7 @@ void Cleanup()
 {
        Log(LOGDEBUG, "Begin cleanup.");
        Sensor_Cleanup();
-       //Actuator_Cleanup();
+       Actuator_Cleanup();
        Log(LOGDEBUG, "Finish cleanup.");
 }
 
@@ -236,23 +236,22 @@ int main(int argc, char ** argv)
 
        
 
-       Sensor_Init();
-       Actuator_Init();
        Pin_Init();
        
        // Try and start things
        
-       const char *ret;
-       if ((ret = Control_SetMode(CONTROL_START, "test")) != NULL)
-               Fatal("Control_SetMode failed with '%s'", ret);
+       //const char *ret;
+       //if ((ret = Control_SetMode(CONTROL_START, "test")) != NULL)
+       //      Fatal("Control_SetMode failed with '%s'", ret);
        
 
        // run request thread in the main thread
        FCGI_RequestLoop(NULL);
 
        
-       if ((ret = Control_SetMode(CONTROL_STOP, "test")) != NULL)
-               Fatal("Control_SetMode failed with '%s'", ret);
+       Control_SetMode(CONTROL_STOP, NULL);
+       //if ((ret = Control_SetMode(CONTROL_STOP, "test")) != NULL)
+       //      Fatal("Control_SetMode failed with '%s'", ret);
        
        //Sensor_StopAll();
        //Actuator_StopAll();
index ae5cdf4..53b3f03 100644 (file)
@@ -47,7 +47,7 @@ int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn
        s->init = init; // Set init function
 
        // Start by averaging values taken over a second
-       DOUBLE_TO_TIMEVAL(1e-4, &(s->sample_time));
+       DOUBLE_TO_TIMEVAL(1, &(s->sample_time));
        s->averages = 1;
        s->num_read = 0;
 
@@ -62,6 +62,8 @@ int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn
 
        s->current_data.time_stamp = 0;
        s->current_data.value = 0;
+       s->averaged_data.time_stamp = 0;
+       s->averaged_data.value = 0;
        return g_num_sensors;
 }
 
@@ -75,16 +77,16 @@ int Sensor_Add(const char * name, int user_id, ReadFn read, InitFn init, CleanFn
 #include "sensors/pressure.h"
 void Sensor_Init()
 {
-       Sensor_Add("cpu_stime", RESOURCE_CPU_SYS, Resource_Read, NULL, NULL, NULL);     
-       Sensor_Add("cpu_utime", RESOURCE_CPU_USER, Resource_Read, NULL, NULL, NULL);    
-       //Sensor_Add("pressure_high0", PRES_HIGH0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
-       //Sensor_Add("pressure_high1", PRES_HIGH1, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
-       //Sensor_Add("pressure_low0", PRES_LOW0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
+       //Sensor_Add("cpu_stime", RESOURCE_CPU_SYS, Resource_Read, NULL, NULL, NULL);   
+       //Sensor_Add("cpu_utime", RESOURCE_CPU_USER, Resource_Read, NULL, NULL, NULL);  
+       Sensor_Add("pressure_high0", PRES_HIGH0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
+       Sensor_Add("pressure_high1", PRES_HIGH1, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
+       Sensor_Add("pressure_low0", PRES_LOW0, Pressure_Read, Pressure_Init, Pressure_Cleanup, NULL);
        //Sensor_Add("../testing/count.py", 0, Piped_Read, Piped_Init, Piped_Cleanup, 1e50,-1e50,1e50,-1e50);
-       //Sensor_Add("strain0", STRAIN0, Strain_Read, Strain_Init, 5000,0,5000,0);
-       //Sensor_Add("strain1", STRAIN1, Strain_Read, Strain_Init, 5000,0,5000,0);
-       //Sensor_Add("strain2", STRAIN2, Strain_Read, Strain_Init, 5000,0,5000,0);
-       //Sensor_Add("strain3", STRAIN3, Strain_Read, Strain_Init, 5000,0,5000,0);
+       Sensor_Add("strain0", STRAIN0, Strain_Read, Strain_Init, Strain_Cleanup, Strain_Sanity);
+       Sensor_Add("strain1", STRAIN1, Strain_Read, Strain_Init, Strain_Cleanup, Strain_Sanity);
+       Sensor_Add("strain2", STRAIN2, Strain_Read, Strain_Init, Strain_Cleanup, Strain_Sanity);
+       Sensor_Add("strain3", STRAIN3, Strain_Read, Strain_Init, Strain_Cleanup, Strain_Sanity);
        //Sensor_Add("pressure0", PRESSURE0, Pressure_Read, Pressure_Init, 5000,0,5000,0);
        //Sensor_Add("pressure1", PRESSURE1, Pressure_Read, Pressure_Init, 5000,0,5000,0);
        //Sensor_Add("pressure_feedback", PRESSURE_FEEDBACK, Pressure_Read, Pressure_Init, 5000,0,5000,0);
@@ -103,6 +105,7 @@ void Sensor_Cleanup()
                if (s->cleanup != NULL)
                        s->cleanup(s->user_id);
        }
+       g_num_sensors = 0;
 }
 
 /**
@@ -183,8 +186,12 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg)
  */
 void Sensor_SetModeAll(ControlModes mode, void * arg)
 {
+       if (mode == CONTROL_START)
+               Sensor_Init();
        for (int i = 0; i < g_num_sensors; i++)
                Sensor_SetMode(&g_sensors[i], mode, arg);
+       if (mode == CONTROL_STOP)
+               Sensor_Cleanup();
 }
 
 
@@ -201,38 +208,40 @@ void * Sensor_Loop(void * arg)
        // Until the sensor is stopped, record data points
        while (s->activated)
        {
-               DataPoint d;
-               d.value = 0;
-               bool success = s->read(s->user_id, &(d.value));
+               
+               bool success = s->read(s->user_id, &(s->current_data.value));
 
                struct timespec t;
                clock_gettime(CLOCK_MONOTONIC, &t);
-               d.time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime());        
+               s->current_data.time_stamp = TIMEVAL_DIFF(t, *Control_GetStartTime());  
                
                if (success)
                {
                        if (s->sanity != NULL)
                        {
-                               if (!s->sanity(s->user_id, d.value))
+                               if (!s->sanity(s->user_id, s->current_data.value))
                                {
                                        Fatal("Sensor %s (%d,%d) reads unsafe value", s->name, s->id, s->user_id);
                                }
                        }
-                       s->current_data.time_stamp += d.time_stamp;
-                       s->current_data.value += d.value;
+                       s->averaged_data.time_stamp += s->current_data.time_stamp;
+                       s->averaged_data.value = s->current_data.value;
                        
                        if (++(s->num_read) >= s->averages)
                        {
-                               s->current_data.time_stamp /= s->averages;
-                               s->current_data.value /= s->averages;
-                               Data_Save(&(s->data_file), &(s->current_data), 1); // Record it
+                               s->averaged_data.time_stamp /= s->averages;
+                               s->averaged_data.value /= s->averages;
+                               Data_Save(&(s->data_file), &(s->averaged_data), 1); // Record it
                                s->num_read = 0;
-                               s->current_data.time_stamp = 0;
-                               s->current_data.value = 0;
+                               s->averaged_data.time_stamp = 0;
+                               s->averaged_data.value = 0;
                        }
                }
                else
-                       Log(LOGWARN, "Failed to read sensor %s (%d,%d)", s->name, s->id,s->user_id);
+               {
+                       // Silence because strain sensors fail ~50% of the time :S
+                       //Log(LOGWARN, "Failed to read sensor %s (%d,%d)", s->name, s->id,s->user_id);
+               }
 
 
                clock_nanosleep(CLOCK_MONOTONIC, 0, &(s->sample_time), NULL);
@@ -408,5 +417,10 @@ const char * Sensor_GetName(int id)
        return g_sensors[id].name;
 }
 
+DataPoint Sensor_LastData(int id)
+{
+       Sensor * s = &(g_sensors[id]);
+       return s->current_data;
+}
 
 
index 0188361..3de48e0 100644 (file)
@@ -61,6 +61,9 @@ typedef struct
        int averages;
        /** Current data **/
        DataPoint current_data;
+
+       /** Summed data **/
+       DataPoint averaged_data;
        /** Number of points read so far before applying average **/
        int num_read;
 
@@ -82,6 +85,8 @@ extern Sensor * Sensor_Identify(const char * str); // Identify a Sensor from a s
 
 extern void Sensor_Handler(FCGIContext *context, char * params); // Handle a FCGI request for Sensor data
 
+extern DataPoint Sensor_LastData(int id);
+
 extern const char * Sensor_GetName(int id);
 
 
index 61cdbcf..29b8d28 100644 (file)
@@ -40,6 +40,9 @@ double Pressure_Callibrate(int id, int adc)
 {
        //double voltage = ADC_TO_VOLTS(adc); // convert reading to voltage
 
+       return (double)adc;
+
+       //TODO: Fix this
        switch (id)
        {
                case PRES_HIGH0:
index 9492de7..77a11a2 100644 (file)
@@ -6,6 +6,8 @@
 #include <pthread.h>
 
 #define STRAIN_ADC ADC0
+// TODO: Choose this
+#define STRAIN_GPIO 15
 
 /**
  * Convert Strain gauge id number to a GPIO pin on the Mux
@@ -59,15 +61,36 @@ bool Strain_Init(const char * name, int id)
        if (!GPIO_Set(gpio_num, false))
                Fatal("Couldn't set GPIO%d for strain sensor %d to LOW", gpio_num, id);
 
-       static bool init_adc = false;
-       if (!init_adc)
+       static int init = 0;
+       if (++init == 1)
        {
-               init_adc = true;
+               GPIO_Export(STRAIN_GPIO);
+               GPIO_Set(STRAIN_GPIO, true);
                ADC_Export(STRAIN_ADC);
        }
        return true;
 }
 
+bool Strain_Cleanup(int id)
+{
+       static int killed = 0;
+       if (++killed == 4)
+       {
+
+               GPIO_Set(STRAIN_GPIO, false);
+               ADC_Unexport(STRAIN_ADC);
+       }
+
+       int gpio_num = Strain_To_GPIO(id);
+       GPIO_Unexport(gpio_num);
+       return true;
+}
+
+bool Strain_Sanity(int id, double value)
+{
+       return true;
+}
+
 /**
  * Read from a Strain gauge
  * @param id - The strain gauge to read
index 1afb625..21b926e 100644 (file)
@@ -19,4 +19,7 @@ extern bool Strain_Init(const char * name, int id);
 // Read from a strain gauge
 extern bool Strain_Read(int id, double * value);
 
+extern bool Strain_Cleanup(int id);
+extern bool Strain_Sanity(int id, double value);
+
 #endif //_STRAIN_H
diff --git a/testing/CansWebInterface/To Be Ammended/UWA_generic3.jpg b/testing/CansWebInterface/To Be Ammended/UWA_generic3.jpg
new file mode 100644 (file)
index 0000000..4d45bb9
Binary files /dev/null and b/testing/CansWebInterface/To Be Ammended/UWA_generic3.jpg differ
diff --git a/testing/CansWebInterface/To Be Ammended/mctx.gui2.js b/testing/CansWebInterface/To Be Ammended/mctx.gui2.js
new file mode 100644 (file)
index 0000000..f779232
--- /dev/null
@@ -0,0 +1,199 @@
+/**
+ * MCTX3420 2013 GUI stuff.
+ */
+
+mctx = {};
+mctx.api = location.protocol + "//" +  location.host + "/api/";
+mctx.expected_api_version = 0;
+mctx.key = undefined;
+mctx.has_control = false;
+
+mctx.return_codes = {
+  "1" : "Ok",
+  "-1" : "General error",
+  "-2" : "Unauthorized",
+  "-3" : "Not running",
+  "-4" : "Already exists"
+};
+
+mctx.sensors = {
+  0 : {name : "Strain gauge 1"},
+  1 : {name : "Strain gauge 2"},
+  2 : {name : "Strain gauge 3"},
+  3 : {name : "Strain gauge 4"},
+  4 : {name : "Pressure sensor 1"},
+  5 : {name : "Pressure sensor 2"}
+};
+
+mctx.actuators = {
+  0 : {name : "Solenoid 1"},
+  1 : {name : "Solenoid 2"},
+  2 : {name : "Solenoid 3"},
+  3 : {name : "Pressure regulator"}
+};
+
+mctx.strain_gauges = {};
+mctx.strain_gauges.ids = [0, 1, 2, 3];
+mctx.strain_gauges.time_limit = 20;
+
+/**
+ * Writes the current date to wherever it's called.
+ */
+function getDate(){
+       document.write((new Date()).toDateString());
+}
+
+/**
+ * Populates a submenu of the navigation bar
+ * @param {string} header The header
+ * @param {object} items An object representing the submenu items
+ * @param {function} translator A function that translates an object item
+ *                              into a text and href.
+ * @returns {$.fn} Itself
+ */
+$.fn.populateSubmenu = function(header, items, translator) {
+  var submenuHeader = $("<li/>").append($("<a/>", {text : header, href : "#"}));
+  var submenu = $("<ul/>", {"class" : "submenu"});
+  
+  for (var item in items) {
+    var info = translator(item, items);
+    submenu.append($("<li/>").append(
+          $("<a/>", {text : info.text, 
+                     href : info.href, target : "_blank"})
+    ));
+  }
+  
+  this.append(submenuHeader.append(submenu));
+  return this;
+};
+
+/** 
+ * Populates the navigation bar
+ */
+$.fn.populateNavbar = function () {
+  var menu = $("<ul/>", {"class" : "menu"});
+  var sensorTranslator = function(item, items) {
+    var href = mctx.api + "sensors?start_time=0&format=tsv&id=" + item;
+    return {text : items[item].name, href : href};
+  };
+  var actuatorTranslator = function(item, items) {
+    var href = mctx.api + "actuators?start_time=0&format=tsv&id=" + item;
+    return {text : items[item].name, href : href};
+  };
+  
+  menu.populateSubmenu("Home", mctx.home, sensorTranslator);
+  menu.populateSubmenu("Setup", mctx.setup, actuatorTranslator);
+  menu.populateSubmenu("Admin", mctx.admin, actuatorTranslator);
+  menu.populateSubmenu("Help", mctx.help, actuatorTranslator);
+  menu.appendTo(this);
+  return this;
+}
+
+/**
+ * Sets the camera autoupdater
+ * @returns {$.fn}
+ */
+$.fn.setCamera = function () {
+  var url = mctx.api + "image";  //http://beaglebone/api/image
+  var update = true;
+
+  //Stop updating if we can't retrieve an image!
+  this.error(function() {
+    update = false;
+  });
+  
+  var parent = this;
+  
+  var updater = function() {
+    if (!update) {
+      alert("Cam fail");
+      parent.attr("src", "");
+      return;
+    }
+    
+    parent.attr("src", url + "#" + (new Date()).getTime());
+    
+    setTimeout(updater, 1000);
+  };
+  
+  updater();
+  return this;
+};
+
+$.fn.setStrainGraphs = function () {
+  var sensor_url = mctx.api + "sensors";
+  var graphdiv = this;
+  
+  var updater = function () {
+    var time_limit = mctx.strain_gauges.time_limit;
+    var responses = new Array(mctx.strain_gauges.ids.length);
+    
+    for (var i = 0; i < mctx.strain_gauges.ids.length; i++) {
+      var parameters = {id : i, start_time: -time_limit};
+      responses[i] = $.ajax({url : sensor_url, data : parameters});
+    }
+    
+    $.when.apply(this, responses).then(function () {
+      var data = new Array(arguments.length);
+      for (var i = 0; i < arguments.length; i++) {
+        var raw_data = arguments[i][0].data;
+        var pruned_data = [];
+        var step = ~~(raw_data.length/100);
+        for (var j = 0; j < raw_data.length; j += step)
+          pruned_data.push(raw_data[j]); 
+        data[i] = pruned_data;
+      }
+      $.plot(graphdiv, data);
+      setTimeout(updater, 500);
+    }, function () {alert("It crashed");});
+  };
+  
+  updater();
+  return this;
+};
+
+$.fn.login = function () {
+  var username = this.find("input[name='username']").val();
+  var password = this.find("input[name='pass']").val();
+  var force = this.find("input[name='force']").is(":checked");
+  var url = mctx.api + "control";
+  
+  var authFunc = function(xhr) {
+    xhr.setRequestHeader("Authorization",
+        "Basic " + base64.encode(username + ":" + password));
+  };
+
+  $.ajax({
+    url : url,
+    data : {action : "lock", force : (force ? true : undefined)},
+    beforeSend : authFunc
+  }).done(function (data) {
+    mctx.key = data.key;
+    if (data.status < 0) {
+      alert("no - " + data.description);
+    } else {
+      mctx.has_control = true;
+      alert("Login Successful - " + mctx.key);
+    }
+  }).fail(function (jqXHR) {
+    mctx.key = undefined;
+    mctx.has_control = false;
+    alert("Login Failed: Please ensure your pheme Username and Password are correct");
+  });
+};
+
+$.fn.setErrorLog = function () {
+  var url = mctx.api + "errorlog";
+  var outdiv = this;
+  
+  var updater = function () {
+    $.ajax({url : url}).done(function (data) {
+      outdiv.text(data);
+      setTimeout(updater, 1000);
+    }).fail(function (jqXHR) {
+      outdiv.text("Failed to retrieve the error log.");
+    });
+  };
+  
+  updater();
+};
\ No newline at end of file
diff --git a/testing/CansWebInterface/To Be Ammended/style.css b/testing/CansWebInterface/To Be Ammended/style.css
new file mode 100644 (file)
index 0000000..7ebfee1
--- /dev/null
@@ -0,0 +1,166 @@
+/** Custom fonts **/
+@font-face {
+  font-family: "Open Sans";
+  src: url("OpenSans.ttf"),
+    url("OpenSans.eot");
+}
+
+/** Limit max width and auto-center content **/
+html  {
+  max-width: 1280px;
+  margin: 0 auto;
+}
+
+body {
+  font-family: "Open Sans", "Lucida Grande","Lucida Sans",Verdana,Arial;
+  font-size: 13px;
+  background-color: #F5F5F5;
+}
+
+hr {
+  border: 0;
+  border-bottom: 1px solid gray;
+}
+
+table {
+  border: none;
+}
+
+table.centre {
+  margin: auto;
+}
+
+table.status, table.status tr, table.status td {
+    padding: 0.2em 0.75em;
+}
+
+th {
+  padding: inherit;
+  border-bottom: 1px solid gray;
+}
+
+img.centre {
+  display: block;
+  margin: auto;
+}
+
+input[type="button"], input[type="submit"] {
+  background-color: #F5F5F5;
+  border: 1px solid #A2A2A2;
+  border-radius: 3px;
+  box-shadow: 1px 1px 1px #BBBBBB;
+  transition: all 0.13s ease 0s;
+}
+
+input[type="button"]:active, input[type="submit"]:active {
+  background-color: #E8E8E8;
+}
+
+/* IE8 width bugfix */
+input[type="text"], input[type="password"] {
+  width: 100%;
+}
+
+#header {
+  background-color: #000000;
+  color: #f2f2f2;
+  padding: 1.5em 2em;
+  margin-top: -1em;
+  margin-bottom: 1em;
+  box-shadow: 0 0 0.5em #444444;
+  border-radius: 4px;
+  transition: all 0.2s ease 0s;
+}
+
+#header #title {
+  font-family: "Open Sans", Arial;
+  font-size: 30px;
+}
+
+#header #rightnav {
+  float: right;
+  padding-top: 0.5em;
+}
+
+#header #menu-container {
+  margin-right: 1.5em;
+  font-size: 15px;
+  display: inline-block;
+}
+
+#header #date {
+  padding-top: 1em;
+  font-size: 12px;
+}
+
+#content  {
+  padding: 1em 2em;
+}
+
+#sidebar{
+  float: right;
+  min-width: 22%;
+}
+
+#sidebar .title {
+  font-size: 20px;
+  font-weight: bold;
+}
+
+#sidebar .item {
+  padding: 0.2em;
+  margin-bottom: 0.5em;
+}
+
+#main {
+  overflow: auto;
+  margin-right: 25%;
+  padding-right: 2em;
+  min-width: 400px;
+}
+
+#main .title {
+  font-size: 24px;
+  font-weight: bold;
+  margin-bottom: 0.5em;
+}
+
+.graph {
+  width: 100%;
+  height: 200px;
+}
+
+.widget {
+  background-color: #EBF4FA;
+  margin: 0.25em 0.25em 1.5em;
+  padding: 1em 1.25em;
+  box-shadow: 0 0 3px #CCCCCC;
+  transition: all 0.2s ease 0s;
+  overflow: auto;
+}
+
+.widget:hover {
+  box-shadow: 0 0 0.25em #AAAAAA;
+}
+
+.widgetPanel {
+  background-color: #B4CFEC;
+  margin: 0.25em 0.25em 1.5em;
+  padding: 1em 1.25em;
+  box-shadow: 0 0 3px #CCCCCC;
+  transition: all 0.2s ease 0s;
+  overflow: auto;
+  /*color: #FFFFFF;*/
+}
+
+/** Hack **/
+.clear {
+  clear: both;
+}
+
+#errorlog {
+  overflow: auto;
+  max-width: 100%;
+  width: 100%;
+  height: 6em;
+}
\ No newline at end of file
diff --git a/testing/CansWebInterface/WidgetCode/controlWidget b/testing/CansWebInterface/WidgetCode/controlWidget
new file mode 100644 (file)
index 0000000..24d6ba9
--- /dev/null
@@ -0,0 +1,26 @@
+<div class="title">Controls</div>
+       <b>Main controls</b>
+       <form id="main_controls" action="">
+               <table>
+                       <tr>
+                               <td>Experiment name</td>
+                               <td><input name="experiment_name" type="text"></td>
+                       </tr>
+                       <tr>
+                       <td>Experiment mode</td>
+                       <td>
+                               <input name="experiment_type" value="strain" type="radio"> Strain it
+                               <input name="experiment_type" value="explode" type="radio"> Explode it
+                       </td>
+                       </tr>
+                       <tr>
+                       <td>
+                       </td>
+                               <td align="right">
+                               <input type="submit" value="Start">
+                               <input type="submit" value="Pause">
+                               <input type="submit" value="Stop">
+                       </td>
+               </tr>
+               </table>
+       </form>
diff --git a/testing/CansWebInterface/WidgetCode/statusPanel.html b/testing/CansWebInterface/WidgetCode/statusPanel.html
new file mode 100644 (file)
index 0000000..67b170f
--- /dev/null
@@ -0,0 +1,13 @@
+ <div class="widgetPanel">
+          <div class="title">Status</div>
+          <div class="item">
+            <table class="status centre">
+              <tr><th>Module</th> <th>State</th></tr>
+              <tr><td>Server API</td> <td>PASS</td></tr>
+              <tr><td>Enclosure interlock</td> <td>FAIL</td></tr>
+              <tr><td>Pressure level</td> <td>PASS</td></tr>
+            </table>
+            <hr>
+            Software mode: <span id="server_mode">off</span>
+          </div>         
+        </div>
\ No newline at end of file
diff --git a/testing/CansWebInterface/WidgetCode/widgetTEMPLATE.txt b/testing/CansWebInterface/WidgetCode/widgetTEMPLATE.txt
new file mode 100644 (file)
index 0000000..4f1141a
--- /dev/null
@@ -0,0 +1,6 @@
+<div class="widgetPanel">
+       <div class="title">ADD TITLE HERE</div>
+       <div>
+               ADD WIDGET CONTENT HERE
+       </div>
+</div>
\ No newline at end of file
diff --git a/testing/CansWebInterface/current/Graph1.png b/testing/CansWebInterface/current/Graph1.png
new file mode 100644 (file)
index 0000000..c8484b6
Binary files /dev/null and b/testing/CansWebInterface/current/Graph1.png differ
diff --git a/testing/CansWebInterface/current/Graph2.png b/testing/CansWebInterface/current/Graph2.png
new file mode 100644 (file)
index 0000000..9c73eb3
Binary files /dev/null and b/testing/CansWebInterface/current/Graph2.png differ
diff --git a/testing/CansWebInterface/current/Graph3.png b/testing/CansWebInterface/current/Graph3.png
new file mode 100644 (file)
index 0000000..6948a84
Binary files /dev/null and b/testing/CansWebInterface/current/Graph3.png differ
diff --git a/testing/CansWebInterface/current/Graph4.png b/testing/CansWebInterface/current/Graph4.png
new file mode 100644 (file)
index 0000000..26e9fd4
Binary files /dev/null and b/testing/CansWebInterface/current/Graph4.png differ
diff --git a/testing/CansWebInterface/current/Graph5.png b/testing/CansWebInterface/current/Graph5.png
new file mode 100644 (file)
index 0000000..2e36c98
Binary files /dev/null and b/testing/CansWebInterface/current/Graph5.png differ
diff --git a/testing/CansWebInterface/current/Graph6.png b/testing/CansWebInterface/current/Graph6.png
new file mode 100644 (file)
index 0000000..86fa22a
Binary files /dev/null and b/testing/CansWebInterface/current/Graph6.png differ
diff --git a/testing/CansWebInterface/current/all.zip b/testing/CansWebInterface/current/all.zip
new file mode 100644 (file)
index 0000000..a12e55d
Binary files /dev/null and b/testing/CansWebInterface/current/all.zip differ
diff --git a/testing/CansWebInterface/current/dummy files to test download functionality.txt b/testing/CansWebInterface/current/dummy files to test download functionality.txt
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/testing/CansWebInterface/current/nograph.png b/testing/CansWebInterface/current/nograph.png
new file mode 100644 (file)
index 0000000..018ba56
Binary files /dev/null and b/testing/CansWebInterface/current/nograph.png differ
diff --git a/testing/CansWebInterface/current/pressure1 b/testing/CansWebInterface/current/pressure1
new file mode 100644 (file)
index 0000000..380f4ae
--- /dev/null
@@ -0,0 +1,2 @@
+Would we be able to put files - like stream into the public - mctxweb file so that the cover page can access the file. 
+wasnt too sure how to go back files without going all the way back to my local filesystem
diff --git a/testing/CansWebInterface/current/pressure2 b/testing/CansWebInterface/current/pressure2
new file mode 100644 (file)
index 0000000..380f4ae
--- /dev/null
@@ -0,0 +1,2 @@
+Would we be able to put files - like stream into the public - mctxweb file so that the cover page can access the file. 
+wasnt too sure how to go back files without going all the way back to my local filesystem
diff --git a/testing/CansWebInterface/current/strain1 b/testing/CansWebInterface/current/strain1
new file mode 100644 (file)
index 0000000..380f4ae
--- /dev/null
@@ -0,0 +1,2 @@
+Would we be able to put files - like stream into the public - mctxweb file so that the cover page can access the file. 
+wasnt too sure how to go back files without going all the way back to my local filesystem
diff --git a/testing/CansWebInterface/current/strain2 b/testing/CansWebInterface/current/strain2
new file mode 100644 (file)
index 0000000..380f4ae
--- /dev/null
@@ -0,0 +1,2 @@
+Would we be able to put files - like stream into the public - mctxweb file so that the cover page can access the file. 
+wasnt too sure how to go back files without going all the way back to my local filesystem
diff --git a/testing/CansWebInterface/current/strain3 b/testing/CansWebInterface/current/strain3
new file mode 100644 (file)
index 0000000..380f4ae
--- /dev/null
@@ -0,0 +1,2 @@
+Would we be able to put files - like stream into the public - mctxweb file so that the cover page can access the file. 
+wasnt too sure how to go back files without going all the way back to my local filesystem
diff --git a/testing/CansWebInterface/current/strain4 b/testing/CansWebInterface/current/strain4
new file mode 100644 (file)
index 0000000..380f4ae
--- /dev/null
@@ -0,0 +1,2 @@
+Would we be able to put files - like stream into the public - mctxweb file so that the cover page can access the file. 
+wasnt too sure how to go back files without going all the way back to my local filesystem
diff --git a/testing/CansWebInterface/dashboard.html b/testing/CansWebInterface/dashboard.html
new file mode 100644 (file)
index 0000000..62d8ad1
--- /dev/null
@@ -0,0 +1,190 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/base64.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+    
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+       <link rel="stylesheet" type="text/css" href="static/exp-menu.css">
+    <script type="text/javascript">
+      $(document).ready(function () {
+        $("#menu-container").populateNavbar();
+        $("#login").submit(function () {
+          $("#login").login();
+          return false;
+        });
+        
+        $("#main_controls").submit(function () {
+          //Validate!
+          return false;
+        });
+        //$("#cam1").setCamera();
+        //$("#strain-graphs").setStrainGraphs();
+        $("#errorlog").setErrorLog();
+      });
+    </script>
+  </head>
+  
+  <body>
+<div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+            src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Can Web Interface: Home</span>
+        </div>
+               <div class="nav-menu">
+                       <ul class="menu">
+                         <li><a href="dashboard.html">Home</a></li>
+                         <li><a href="setup.html">Setup</a></li>
+                         <li><a href="admin.html">Admin</a></li>
+                         <li><a href="help.html">Help</a></li>
+                       </ul>
+               </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>    <!-- End header -->
+    
+    <div id="content">
+      <div id="sidebar">
+               <div class="widgetPanel">
+                       <div class="title">Controls</div>
+                       <form id="main_controls" action="">
+                               <table>
+                               <tr>
+                                       <td>Experiment name</td>
+                                       <td>
+                                       <input name="experiment_name" type="text">
+                                       </td>
+                               </tr>
+                               <tr>
+                               <td>Experiment mode</td>
+                               <td>
+                                       <input name="experiment_type" value="strain" type="radio"> Strain it
+                                       <input name="experiment_type" value="explode" type="radio"> Explode it
+                               </td>
+                               </tr>
+                               </table>
+                               <div align="right">
+                                       <input type="submit" value="Start">
+                                       <input type="submit" value="Pause">
+                                       <input type="submit" value="Stop">
+                               </div>
+                       </form>
+               </div>
+        <div class="widgetPanel">
+          <div class="title">Status</div>
+          <div class="item">
+            <table class="status centre">
+              <tr><th>Module</th> <th>State</th></tr>
+              <tr><td>Server API</td> <td>PASS</td></tr>
+              <tr><td>Enclosure interlock</td> <td>FAIL</td></tr>
+              <tr><td>Pressure level</td> <td>PASS</td></tr>
+            </table>
+            <hr>
+            Software mode: <span id="server_mode">off</span>
+          </div>         
+        </div>
+        
+        <div class="widgetPanel">
+          <div class="title">Pressure controls</div>
+        </div>
+       </div>
+       <!-- End sidebar -->
+
+      <div id="main">
+         <div class="widget">
+               <div class="exp-menu">
+                       <ul class="menu">
+                         <li><a href="#"><b>Dashboard</b></a></li>
+                         <li><a href="graph.html">Graph</a></li>
+                         <li><a href="#">Image</a></li>
+                         <li><a href="#">In/Out</a></li>
+                       </ul>
+                 </div>
+               </div>
+               <div class="widget">
+            <div class="title">System Diagram</div>
+            <img id="sbd" src="static/sbd4.png" class="centre" style="border:5px solid" alt="System diagram" usemap="#sbd-map">
+            <map name="sbd-map">
+              <area shape="rect" coords="8,72,105,118" href="#" alt="Client PC" title="You">
+              <area shape="rect" coords="176,72,275,118" href="#" alt="BBB">
+              <area shape="rect" coords="298,53,395,145" href="#" alt="Electronics">
+              <area shape="rect" coords="446,53,543,145" href="#" alt="Pneumatics">
+              <area shape="rect" coords="218,191,315,237" href="#" alt="Sensors">
+              <area shape="rect" coords="418,191,515,237" href="#" alt="Camera">
+              <area shape="rect" coords="218,237,315,285" href="#" alt="Strain Can">
+              <area shape="rect" coords="418,237,515,285" href="#" alt="Explode Can" title="Won't happen">
+              <area shape="rect" coords="87,191,183,237" href="#" alt="Case" id="case-btn">
+              <area shape="rect" coords="87,257,183,303" href="#" alt="Mounting" id="mounting-btn">
+              <area shape="rect" coords="210,182,540,317" alt="Mounting Area" id="mounting-area">
+              <area shape="poly" coords="172,27,548,27,548,317,212,317,212,156,172,156" alt="case Area" id="case-area">
+            </map>
+            <script type="text/javascript" src="static/jquery.maphilight.min.js">
+
+            </script>
+            <script type="text/javascript">
+              $("#sbd").maphilight({
+                fillOpacity: 0.4,
+                strokeColor: '000000',
+                strokeOpacity: 0.7
+              });
+
+              $.fn.hilightToggle = function (on) {
+                var data = $(this).data("maphilight") || {};
+                data.neverOn = !on;
+                $(this).data("maphilight", data);
+                return this;
+              };
+
+              $("#mounting-area").hilightToggle(false);
+              $("#case-area").hilightToggle(false);
+
+              $("#mounting-btn").mouseover(function () {
+                $("#mounting-area").hilightToggle(true).mouseover();
+              }).mouseout(function () {
+                $("#mounting-area").hilightToggle(false);
+              });
+
+              $("#case-btn").mouseover(function () {
+                $("#case-area").hilightToggle(true).mouseover();
+              }).mouseout(function () {
+                $("#case-area").hilightToggle(false);
+              });
+
+            </script>
+            
+            <div class="clear"></div>
+          </div>
+                 </div>
+
+
+       <!-- End main content -->
+      
+    </div>
+  </body>
+</html>
diff --git a/testing/CansWebInterface/graph.html b/testing/CansWebInterface/graph.html
new file mode 100644 (file)
index 0000000..350ed14
--- /dev/null
@@ -0,0 +1,195 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/base64.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+    
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+       <link rel="stylesheet" type="text/css" href="static/exp-menu.css">
+    <script type="text/javascript">
+      $(document).ready(function () {
+        $("#menu-container").populateNavbar();
+        $("#login").submit(function () {
+          $("#login").login();
+          return false;
+        });
+        
+        $("#main_controls").submit(function () {
+          //Validate!
+          return false;
+        });
+        //$("#cam1").setCamera();
+        //$("#strain-graphs").setStrainGraphs();
+        $("#errorlog").setErrorLog();
+       
+      });
+    </script>
+       <script type="text/javascript">
+      runBeforeLoad().always(function () {
+        $(document).ready(function () {
+          $("#graph-controls").setDevices();
+          $("#graph-run").runButton();
+          
+       });       
+      })
+
+    </script>
+  </head>
+  
+  <body>
+<div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+            src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Can Web Interface: Home</span>
+        </div>
+               <div class="nav-menu">
+                       <ul class="menu">
+                         <li><a href="dashboard.html">Home</a></li>
+                         <li><a href="setup.html">Setup</a></li>
+                         <li><a href="admin.html">Admin</a></li>
+                         <li><a href="help.html">Help</a></li>
+                       </ul>
+               </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>    <!-- End header -->
+    
+    <div id="content">
+      <div id="sidebar">
+         <div class="widgetPanel">
+                       <div class="title">Controls</div>
+                       <form id="main_controls" action="">
+                               <table>
+                               <tr>
+                                       <td>Experiment name</td>
+                                       <td>
+                                       <input name="experiment_name" type="text">
+                                       </td>
+                               </tr>
+                               <tr>
+                               <td>Experiment mode</td>
+                               <td>
+                                       <input name="experiment_type" value="strain" type="radio"> Strain it
+                                       <input name="experiment_type" value="explode" type="radio"> Explode it
+                               </td>
+                               </tr>
+                               </table>
+                               <div align="right">
+                                       <input type="submit" value="Start">
+                                       <input type="submit" value="Pause">
+                                       <input type="submit" value="Stop">
+                               </div>
+                       </form>
+               </div>
+        <div class="widgetPanel">
+          <div class="title">Status</div>
+          <div class="item">
+            <table class="status centre">
+              <tr><th>Module</th> <th>State</th></tr>
+              <tr><td>Server API</td> <td>PASS</td></tr>
+              <tr><td>Enclosure interlock</td> <td>FAIL</td></tr>
+              <tr><td>Pressure level</td> <td>PASS</td></tr>
+            </table>
+            <hr>
+            Software mode: <span id="server_mode">off</span>
+          </div>         
+        </div>
+        
+        <div class="widgetPanel">
+          <div class="title">Pressure controls</div>
+        </div>
+        
+       </div>
+       <!-- End sidebar -->
+
+      <div id="main">
+         <!--Experiment Menu Bar-->
+               <div class="widget">
+               <div class="exp-menu">
+                       <ul class="menu">
+                         <li><a href="dashboard.html">Dashboard</a></li>
+                         <li><a href="#"><b>Graph</b></a></li>
+                         <li><a href="#">Image</a></li>
+                         <li><a href="#">In/Out</a></li>
+                       </ul>
+                 </div>
+               </div>
+               
+               <div class="widget">
+            <div class="title">Graph</div>
+              <div id="graph" class="plot">
+                <!-- graph placeholder -->
+              </div>
+          </div>
+          <div class="widget" id="graph-controls">
+              <!--<div class="title">Visualise</div>-->
+              <b>X-Axis</b>
+                <form id="xaxis" onChange=$("#graph").setGraph()> <input type="radio" name="xaxis" value="time" id="time" checked="yes">time</input> </form>
+                <b>Y-Axis</b>
+                <form id="yaxis" onChange=$("#graph").setGraph()>  </form>
+                <!--b>Right Y-Axis</b>
+                <form id="y2axis" onChange=$("#graph").setGraph()> <input type="radio" name="y2axis" value="none" id="none" checked="yes">none</input></form>-->
+
+                <div> 
+                  <form id="time_range" onChange=$("#graph").setGraph()>
+                    <p> 
+                        Time of Last Update <input type="text" value="" id="current_time" disabled></input>
+                        Start Time <input type="text" value="" id="start_time"></input> 
+                        End Time <input type="text" value="" id="end_time"></input>
+                    </p>
+                </form>
+                </div>
+                <input type="button" value="Run" id="graph-run" onClick=$("#graph-run").runButton()></input>
+                <input type="button" value="Open New Graph" disabled></input>
+                <input type="button" value="Save Graph Image" disabled></input>
+                <input type="button" value="Dump Raw Data" disabled></input>
+            </div>
+        
+        <!--TODO put this on new page??
+          <div class="widget">
+              <div class="title">Controls</div>
+              <form id="controls">
+                <p>Mode: <input type="radio" name="mode" value="normal">Normal</input>
+                      <input type="radio" name="mode" value="explode">EXPLODE!</input> </p>
+                Filename: <input type="text"> </input>
+                <input type="button" value="Stop" id="stopstart"></input>
+                <input type="button" value="Pause" id="runpause"></input>
+                <input type="button" value="Load"></input>
+                
+              </form>
+          </div>
+                 -->
+                 </div>
+
+
+       <!-- End main content -->
+      
+    </div>
+  </body>
+</html>
diff --git a/testing/CansWebInterface/help.html b/testing/CansWebInterface/help.html
new file mode 100644 (file)
index 0000000..6340cc6
--- /dev/null
@@ -0,0 +1,199 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/base64.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+    
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+    <script type="text/javascript">
+      $(document).ready(function () {
+        $("#menu-container").populateNavbar();
+        $("#login").submit(function () {
+          $("#login").login();
+          return false;
+        });
+        
+        $("#main_controls").submit(function () {
+          //Validate!
+          return false;
+        });
+        //$("#cam1").setCamera();
+        //$("#strain-graphs").setStrainGraphs();
+        $("#errorlog").setErrorLog();
+      });
+    </script>
+  </head>
+  
+  <body>
+<div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+            src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Can Web Interface: Help</span>
+        </div>
+               <div class="nav-menu">
+                       <ul class="menu">
+                         <li><a href="dashboard.html">Home</a></li>
+                         <li><a href="setup.html">Setup</a></li>
+                         <li><a href="admin.html">Admin</a></li>
+                         <li><a href="help.html">Help</a></li>
+                       </ul>
+               </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>    
+       <!-- End header -->
+    
+    <div id="content">
+      <div id="sidebar">
+        <div class="widgetPanel">
+          <div class="title">Status</div>
+          <div class="item">
+            <table class="status centre">
+              <tr><th>Module</th> <th>State</th></tr>
+              <tr><td>Server API</td> <td>PASS</td></tr>
+              <tr><td>Enclosure interlock</td> <td>FAIL</td></tr>
+              <tr><td>Pressure level</td> <td>PASS</td></tr>
+            </table>
+            <hr>
+            Software mode: <span id="server_mode">off</span>
+          </div>         
+        </div>
+        
+               <div class="widgetPanel">
+            <div class="title">Tutorials</div>
+            <p class="justify">For general information on using the MCTX project web interface (i.e. this webpage!) see the <b>'Tutorials'</b> section.
+              This will guide you through setting up, running and analysing an experiment with basic tutorials and explanation of the possible options.</p>
+          </div>
+          <div class="widgetPanel">
+            <div class="title">Experiment Hardware</div>
+            <p class="justify">For detailed technical information about the experiment hardware, including system diagrams, datasheets and run-downs of the components,
+              see the <b>'Hardware'</b> section.</p>
+          </div>
+          <div class="widgetPanel">
+            <div class="title">Experiment Software</div>
+            <p class="justify">For detailed technical information about the experiment software, including explanations of its structure, functions and so on, see the
+              <b>'Software'</b> section. Actual code can be accessed on the project's GitHub: <a href="https://github.com/szmoore/MCTX3420">MCTX3420</a>.</p>
+          </div>
+          <div class="widgetPanel">
+            <div class="title">Contact</div>
+            <p class="justify">If you would like more information about the project or require additional assistance, please contact:</p>
+            <p><b>Adrian Keating</b></p>
+            <p><b>Adam Wittek</b></p>
+          </div>
+
+        
+       </div>
+
+       <!-- End sidebar -->
+
+        <div id="main">
+          <div class="widget">
+            <div class="title">Getting Started</div>
+            <p class="justify">Welcome to the MCTX3420 project documentation! On this page, you will find help on how to use the project software,
+              as well as information about the various hardware components.</p>
+            <p class="justify">For a basic introduction to using the experiment, see <b>'Tutorials'</b>.
+              <a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a> provides a general overview of the experiment's hardware, while
+              <a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Using the web interface</a> provides a tutorial on using the the project software.
+              For detailed information about the experiment hardware, see the <b>'Hardware'</b> section. For detailed information about the project software,
+              see the <b>'Software'</b> section.</p>
+            <div class="sub-title">Tutorials</div>
+            <ul>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Using the web interface</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Run-an-Experiment" target="_blank">Running an experiment</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-View-Old-Experiments" target="_blank">Viewing previous experiments</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Data-Processing" target="_blank">Data processing</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Safety" target="_blank">System safety</a></li>
+            </ul>
+            <div class="sub-title">Hardware</div>
+            <ul>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Electronics" target="_blank">Electronics</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-BeagleBone" target="_blank">BeagleBone</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Pneumatics" target="_blank">Pneumatics</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors" target="_blank">Sensors</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Mounting" target="_blank">Mounting</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Case" target="_blank">Case</a></li>
+            </ul>
+            <div class="sub-title">Software</div>
+            <ul>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Overview" target="_blank">Software overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Browser-Interface" target="_blank">Browser interface</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-API" target="_blank">Server API</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-Code" target="_blank">Server code</a>
+                <ul>
+                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Data-Handling" target="_blank">Data handling</a></li>
+                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Sensors-&amp;-Actuators" target="_blank">Sensors and actuators</a></li>
+                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Pin-Access" target="_blank">Pin access</a></li>
+                  <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Image-Processing" target="_blank">Image processing</a></li>
+                </ul>
+              </li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Terminology" target="_blank">Terminology</a></li>
+            </ul>
+          </div>
+
+          <div class="widget">
+            <div class="title">Project Wiki</div>
+            <p>All of the project's help documentation is stored in its <a href="https://github.com/szmoore/MCTX3420/wiki" target="_blank">GitHub wiki.</a> This git repository also
+              stores all of the software code for the project; to access the project code files, visit <a href="https://github.com/szmoore/MCTX3420" target="_blank">this link</a>.</p>
+            <p>The following is a list of pages on the GitHub wiki (all of which can be accessed via the categories above):<p>
+            <ul>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Data-Processing" target="_blank">Getting Started: Data Processing</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Run-an-Experiment" target="_blank">Getting Started: Run an Experiment</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-View-Old-Experiments" target="_blank">Getting Started: View Old Experiments</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Getting-Started:-Web-Interface" target="_blank">Getting Started: Web Interface</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-BeagleBone" target="_blank">Hardware: BeagleBone</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Case" target="_blank">Hardware: Case</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Electronics" target="_blank">Hardware: Electronics</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Mounting" target="_blank">Hardware: Mounting</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Pneumatics" target="_blank">Hardware: Pneumatics</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors" target="_blank">Hardware: Sensors</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki" target="_blank">Home</a>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Browser-Interface" target="_blank">Software: Browser Interface</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Data-Handling" target="_blank">Software: Data Handling</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Image-Processing" target="_blank">Software: Image Processing</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Overview" target="_blank">Software: Overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Pin-Access" target="_blank">Software: Pin Access</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Sensors-&amp;-Actuators" target="_blank">Software: Sensors &amp; Actuators</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-API" target="_blank">Software: Server API</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Server-Code" target="_blank">Software: Server Code</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/Software:-Terminology" target="_blank">Software: Terminology</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Overview" target="_blank">System Overview</a></li>
+              <li><a href="https://github.com/szmoore/MCTX3420/wiki/System-Safety" target="_blank">System Safety</a></li>
+            </ul>
+          </div>
+        </div>
+               </div>
+
+
+       <!-- End main content -->
+      
+    </div>
+  </body>
+</html>
diff --git a/testing/CansWebInterface/index.html b/testing/CansWebInterface/index.html
new file mode 100644 (file)
index 0000000..7061f8d
--- /dev/null
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/base64.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+    
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+    <script type="text/javascript">
+      $(document).ready(function () {
+        $("#menu-container").populateNavbar();
+        $("#login").submit(function () {
+          $("#login").login();
+          return false;
+        });
+        
+        $("#main_controls").submit(function () {
+          //Validate!
+          return false;
+        });
+        //$("#cam1").setCamera();
+        //$("#strain-graphs").setStrainGraphs();
+        $("#errorlog").setErrorLog();
+      });
+    </script>
+  </head>
+  
+  <body>
+    <div id="header">
+      <span id="title">Exploding Cans</span>
+      <div id="rightnav">
+        <div id="menu-container" class="nav-menu">
+        </div>
+        <span id="date">
+          <script type="text/javascript">getDate();</script>
+        </span>
+      </div>
+      <div class="clear"></div>
+    </div>
+    <!-- End header -->
+    
+    <div id="content">
+      <div id="sidebar">
+        <div class="widget">
+          <div class="title">Status</div>
+          <div class="item">
+            <table class="status centre">
+              <tr><th>Module</th> <th>State</th></tr>
+              <tr><td>Server API</td> <td>PASS</td></tr>
+              <tr><td>Enclosure interlock</td> <td>FAIL</td></tr>
+              <tr><td>Pressure level</td> <td>PASS</td></tr>
+            </table>
+            <hr>
+            Software mode: <span id="server_mode">off</span>
+          </div>         
+        </div>
+        
+        <div class="widget">
+          <div class="title">Pressure controls</div>
+        </div>
+        
+        <div class="widget">
+          <div class="title">Login</div>
+          <div class="item">
+            <form id="login" action="#">
+              <table class="centre">
+                <tr><td>Username</td><td><input name="username" type="text"></td></tr>
+                <tr><td>Password</td><td><input name="pass" type="password"></td></tr>
+                <tr>
+                  <td></td>
+                  <td>
+                    <input type="submit" value="Submit">
+                    <input type="checkbox" name="force"> Force
+                  </td>
+                </tr>
+              </table>
+            </form>
+          </div>
+        </div>
+      </div>
+      <!-- End sidebar -->
+
+      <div id="main">
+        <div class="widget">
+          <div class="title">Dashboard</div>
+          <!--<img class="centre" src="overview.png" alt="Overview">-->
+          <b>Main controls</b>
+          <form id="main_controls" action="">
+            <table>
+              <tr>
+                <td>Experiment name</td>
+                <td><input name="experiment_name" type="text"></td>
+              </tr>
+              <tr>
+                <td>Experiment mode</td>
+                <td>
+                  <input name="experiment_type" value="strain" type="radio"> Strain it
+                  <input name="experiment_type" value="explode" type="radio"> Explode it
+                </td>
+              </tr>
+              <tr>
+                <td>
+                </td>
+                <td align="right">
+                  <input type="submit" value="Start">
+                </td>
+              </tr>
+            </table>
+          </form>
+          <b>Error and warning messages</b><br>
+          <textarea id="errorlog" wrap="off" rows="4" cols="30" readonly></textarea>
+        </div>
+        
+        <div class="widget">
+          <div class="title">Strain gauges</div>
+          <div id="strain-graphs" class="graph">
+            <!-- Strain graph placeholder -->
+          </div>
+        </div>
+        
+        <div class="widget">
+          <div class="title">Camera Feed</div>
+          <img src="" alt="Camera 1" id="cam1" class="centre">
+        </div>
+      </div>
+      <!-- End main content -->
+      
+    </div>
+  </body>
+</html>
diff --git a/testing/CansWebInterface/index2.html b/testing/CansWebInterface/index2.html
new file mode 100644 (file)
index 0000000..0846cf6
--- /dev/null
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/base64.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+    
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+    <script type="text/javascript">
+      $(document).ready(function () {
+        $("#menu-container").populateNavbar();
+        $("#login").submit(function () {
+          $("#login").login();
+          return false;
+        });
+        
+        $("#main_controls").submit(function () {
+          //Validate!
+          return false;
+        });
+        //$("#cam1").setCamera();
+        //$("#strain-graphs").setStrainGraphs();
+        $("#errorlog").setErrorLog();
+       
+      });
+    </script>
+       <script type="text/javascript">
+      runBeforeLoad().always(function () {
+        $(document).ready(function () {
+          $("#graph-controls").setDevices();
+          $("#graph-run").runButton();
+          
+       });       
+      })
+
+    </script>
+  </head>
+  
+  <body>
+<div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+            src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Can Web Interface: Login</span>
+        </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>    <!-- End header -->
+    
+    <div id="content">
+      <div id="sidebar">
+        <div class="widgetPanel">
+          <div class="title">Status</div>
+          <div class="item">
+            <table class="status centre">
+              <tr><th>Module</th> <th>State</th></tr>
+              <tr><td>Server API</td> <td>PASS</td></tr>
+              <tr><td>Enclosure interlock</td> <td>FAIL</td></tr>
+              <tr><td>Pressure level</td> <td>PASS</td></tr>
+            </table>
+            <hr>
+            Software mode: <span id="server_mode">off</span>
+          </div>         
+        </div>
+        
+        <div class="widgetPanel">
+          <div class="title">Pressure controls</div>
+        </div>
+        
+       </div>
+       <!-- End sidebar -->
+
+      <div id="main">
+        <div class="widget">
+          <div class="title">Welcome</div>
+          <!--<img class="centre" src="overview.png" alt="Overview">-->
+                       <b>Introduction To Exploding Cans</b>
+                       <p>
+                Welcome to the MCTX3420 remote pressure vessel experiment site!
+              </p>
+              <p>
+                To explore how this system works, hover over the elements of the
+                system diagram below. Clicking each element will lead to a new
+                page that briefly describes that component of the system.
+              </p>
+              <p>
+                For the full documentation, see the project wiki at:
+                <a href="https://github.com/szmoore/MCTX3420/wiki">
+                  https://github.com/szmoore/MCTX3420/wiki
+                </a>
+              </p>
+
+                       <p>
+                               If you wish to learn more about the system or would like some assistance please click on the help link at the top of the page. This can be accessed at any time. 
+                               In order to access the Experiment Interface you must login below using your pheme username and password.
+                       </p>
+        </div>
+               
+               <!--LOGIN FIELD-->
+        <div class="widget">
+          <div class="title">Login</div>
+                 <p>
+                 Please use your pheme username and password.
+                 </p>
+          <div class="item">
+            <form id="login" action="#">
+              <table class="centre">
+                <tr><td>Username</td><td><input name="username" type="text"></td></tr>
+                <tr><td>Password</td><td><input name="pass" type="password"></td></tr>
+                <tr>
+                  <td></td>
+                  <td>
+                    <input type="submit" value="Submit">
+                  </td>
+                </tr>
+              </table>
+            </form>
+          </div>
+        </div>
+               
+               </div>
+
+
+       <!-- End main content -->
+      
+    </div>
+  </body>
+</html>
diff --git a/testing/CansWebInterface/index3.html b/testing/CansWebInterface/index3.html
new file mode 100644 (file)
index 0000000..3bccb8a
--- /dev/null
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/base64.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+    
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+    <script type="text/javascript">
+      $(document).ready(function () {
+        $("#login").submit(function () {
+          $("#login").login();
+          return false;
+        });
+        
+        $("#main_controls").submit(function () {
+          //Validate!
+          return false;
+        });
+        //$("#cam1").setCamera();
+        //$("#strain-graphs").setStrainGraphs();
+        $("#errorlog").setErrorLog();
+               
+       
+      });
+    </script>
+       <script>
+               window.onload = function() {
+               $( "#statusModule" ).load("WidgetCode/statusPanel.html");
+               };
+       </script>
+  </head>
+  
+  <body>
+<div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+            src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Can Web Interface: Login</span>
+        </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+                 
+          <span id="date">
+            <script type="text/javascript">
+                       getDate();
+                       </script>
+          </span>
+                 <p> Ver. 1.0</p>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>    <!-- End header -->
+    
+    <div id="content">
+      <div id="sidebar">
+               <div id="statusModule">
+               </div>
+               <!--
+        <div class="widgetPanel">
+          <div class="title">Pressure controls</div>
+        </div>
+               -->
+        
+       </div>
+       <!-- End sidebar -->
+
+      <div id="main">
+        <div class="widget">
+          <div class="title">Welcome</div>
+          <!--<img class="centre" src="overview.png" alt="Overview">-->
+                       <b>Introduction To Exploding Cans</b>
+                       <p>
+                Welcome to the MCTX3420 remote pressure vessel experiment site!
+              </p>
+              <p>
+                To explore how this system works, hover over the elements of the
+                system diagram below. Clicking each element will lead to a new
+                page that briefly describes that component of the system.
+              </p>
+              <p>
+                For the full documentation, see the project wiki at:
+                <a href="https://github.com/szmoore/MCTX3420/wiki">
+                  https://github.com/szmoore/MCTX3420/wiki
+                </a>
+              </p>
+
+                       <p>
+                               If you wish to learn more about the system or would like some assistance please click on the help link at the top of the page. This can be accessed at any time. 
+                               In order to access the Experiment Interface you must login below using your pheme username and password.
+                       </p>
+        </div>
+               
+               <!--LOGIN FIELD-->
+        <div class="widget">
+          <div class="title">Login</div>
+                 <p>
+                 Please use your pheme username and password.
+                 </p>
+          <div class="item">
+            <form id="login" action="#">
+             <p>
+               <label>
+                 Username<br>
+                 <input name="username" type="text">
+               </label>
+             </p>
+             <p>
+               <label>
+                 Password<br>
+                 <input name="pass" type="password">
+               </label>             
+             </p>
+             <p style="float:left; margin:0;">
+               <a href="#">Forgotten password?</a>
+             </p>
+             <p style="float:right; margin:0;">
+               <input type="submit" value="Log In">
+             </p>
+             <p id="result">
+               &nbsp;
+             </p>
+            </form>
+            </form>
+          </div>
+        </div>
+               
+               </div>
+
+
+       <!-- End main content -->
+      
+    </div>
+  </body>
+</html>
diff --git a/testing/CansWebInterface/static/OpenSans.eot b/testing/CansWebInterface/static/OpenSans.eot
new file mode 100644 (file)
index 0000000..a0229f5
Binary files /dev/null and b/testing/CansWebInterface/static/OpenSans.eot differ
diff --git a/testing/CansWebInterface/static/OpenSans.ttf b/testing/CansWebInterface/static/OpenSans.ttf
new file mode 100644 (file)
index 0000000..959c9a4
Binary files /dev/null and b/testing/CansWebInterface/static/OpenSans.ttf differ
diff --git a/testing/CansWebInterface/static/base64.js b/testing/CansWebInterface/static/base64.js
new file mode 100644 (file)
index 0000000..72bced4
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2010 Nick Galbreath
+ * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/* base64 encode/decode compatible with window.btoa/atob
+ *
+ * window.atob/btoa is a Firefox extension to convert binary data (the "b")
+ * to base64 (ascii, the "a").
+ *
+ * It is also found in Safari and Chrome.  It is not available in IE.
+ *
+ * if (!window.btoa) window.btoa = base64.encode
+ * if (!window.atob) window.atob = base64.decode
+ *
+ * The original spec's for atob/btoa are a bit lacking
+ * https://developer.mozilla.org/en/DOM/window.atob
+ * https://developer.mozilla.org/en/DOM/window.btoa
+ *
+ * window.btoa and base64.encode takes a string where charCodeAt is [0,255]
+ * If any character is not [0,255], then an DOMException(5) is thrown.
+ *
+ * window.atob and base64.decode take a base64-encoded string
+ * If the input length is not a multiple of 4, or contains invalid characters
+ *   then an DOMException(5) is thrown.
+ */
+var base64 = {};
+base64.PADCHAR = '=';
+base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+
+base64.makeDOMException = function() {
+    // sadly in FF,Safari,Chrome you can't make a DOMException
+    var e, tmp;
+
+    try {
+        return new DOMException(DOMException.INVALID_CHARACTER_ERR);
+    } catch (tmp) {
+        // not available, just passback a duck-typed equiv
+        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
+        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
+        var ex = new Error("DOM Exception 5");
+
+        // ex.number and ex.description is IE-specific.
+        ex.code = ex.number = 5;
+        ex.name = ex.description = "INVALID_CHARACTER_ERR";
+
+        // Safari/Chrome output format
+        ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
+        return ex;
+    }
+}
+
+base64.getbyte64 = function(s,i) {
+    // This is oddly fast, except on Chrome/V8.
+    //  Minimal or no improvement in performance by using a
+    //   object with properties mapping chars to value (eg. 'A': 0)
+    var idx = base64.ALPHA.indexOf(s.charAt(i));
+    if (idx === -1) {
+        throw base64.makeDOMException();
+    }
+    return idx;
+}
+
+base64.decode = function(s) {
+    // convert to string
+    s = '' + s;
+    var getbyte64 = base64.getbyte64;
+    var pads, i, b10;
+    var imax = s.length
+    if (imax === 0) {
+        return s;
+    }
+
+    if (imax % 4 !== 0) {
+        throw base64.makeDOMException();
+    }
+
+    pads = 0
+    if (s.charAt(imax - 1) === base64.PADCHAR) {
+        pads = 1;
+        if (s.charAt(imax - 2) === base64.PADCHAR) {
+            pads = 2;
+        }
+        // either way, we want to ignore this last block
+        imax -= 4;
+    }
+
+    var x = [];
+    for (i = 0; i < imax; i += 4) {
+        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
+            (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
+        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
+    }
+
+    switch (pads) {
+    case 1:
+        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
+        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
+        break;
+    case 2:
+        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
+        x.push(String.fromCharCode(b10 >> 16));
+        break;
+    }
+    return x.join('');
+}
+
+base64.getbyte = function(s,i) {
+    var x = s.charCodeAt(i);
+    if (x > 255) {
+        throw base64.makeDOMException();
+    }
+    return x;
+}
+
+base64.encode = function(s) {
+    if (arguments.length !== 1) {
+        throw new SyntaxError("Not enough arguments");
+    }
+    var padchar = base64.PADCHAR;
+    var alpha   = base64.ALPHA;
+    var getbyte = base64.getbyte;
+
+    var i, b10;
+    var x = [];
+
+    // convert to string
+    s = '' + s;
+
+    var imax = s.length - s.length % 3;
+
+    if (s.length === 0) {
+        return s;
+    }
+    for (i = 0; i < imax; i += 3) {
+        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
+        x.push(alpha.charAt(b10 >> 18));
+        x.push(alpha.charAt((b10 >> 12) & 0x3F));
+        x.push(alpha.charAt((b10 >> 6) & 0x3f));
+        x.push(alpha.charAt(b10 & 0x3f));
+    }
+    switch (s.length - imax) {
+    case 1:
+        b10 = getbyte(s,i) << 16;
+        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
+               padchar + padchar);
+        break;
+    case 2:
+        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
+        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
+               alpha.charAt((b10 >> 6) & 0x3f) + padchar);
+        break;
+    }
+    return x.join('');
+}
+
+
diff --git a/testing/CansWebInterface/static/excanvas.min.js b/testing/CansWebInterface/static/excanvas.min.js
new file mode 100644 (file)
index 0000000..fcf876c
--- /dev/null
@@ -0,0 +1 @@
+if(!document.createElement("canvas").getContext){(function(){var ab=Math;var n=ab.round;var l=ab.sin;var A=ab.cos;var H=ab.abs;var N=ab.sqrt;var d=10;var f=d/2;var z=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];function y(){return this.context_||(this.context_=new D(this))}var t=Array.prototype.slice;function g(j,m,p){var i=t.call(arguments,2);return function(){return j.apply(m,i.concat(t.call(arguments)))}}function af(i){return String(i).replace(/&/g,"&amp;").replace(/"/g,"&quot;")}function Y(m,j,i){if(!m.namespaces[j]){m.namespaces.add(j,i,"#default#VML")}}function R(j){Y(j,"g_vml_","urn:schemas-microsoft-com:vml");Y(j,"g_o_","urn:schemas-microsoft-com:office:office");if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j<m.length;j++){this.initElement(m[j])}},initElement:function(j){if(!j.getContext){j.getContext=y;R(j.ownerDocument);j.innerHTML="";j.attachEvent("onpropertychange",x);j.attachEvent("onresize",W);var i=j.attributes;if(i.width&&i.width.specified){j.style.width=i.width.nodeValue+"px"}else{j.width=j.clientWidth}if(i.height&&i.height.specified){j.style.height=i.height.nodeValue+"px"}else{j.height=j.clientHeight}}return j}};function x(j){var i=j.srcElement;switch(j.propertyName){case"width":i.getContext().clearRect();i.style.width=i.attributes.width.nodeValue+"px";i.firstChild.style.width=i.clientWidth+"px";break;case"height":i.getContext().clearRect();i.style.height=i.attributes.height.nodeValue+"px";i.firstChild.style.height=i.clientHeight+"px";break}}function W(j){var i=j.srcElement;if(i.firstChild){i.firstChild.style.width=i.clientWidth+"px";i.firstChild.style.height=i.clientHeight+"px"}}e.init();var k=[];for(var ae=0;ae<16;ae++){for(var ad=0;ad<16;ad++){k[ae*16+ad]=ae.toString(16)+ad.toString(16)}}function B(){return[[1,0,0],[0,1,0],[0,0,1]]}function J(p,m){var j=B();for(var i=0;i<3;i++){for(var ah=0;ah<3;ah++){var Z=0;for(var ag=0;ag<3;ag++){Z+=p[i][ag]*m[ag][ah]}j[i][ah]=Z}}return j}function v(j,i){i.fillStyle=j.fillStyle;i.lineCap=j.lineCap;i.lineJoin=j.lineJoin;i.lineWidth=j.lineWidth;i.miterLimit=j.miterLimit;i.shadowBlur=j.shadowBlur;i.shadowColor=j.shadowColor;i.shadowOffsetX=j.shadowOffsetX;i.shadowOffsetY=j.shadowOffsetY;i.strokeStyle=j.strokeStyle;i.globalAlpha=j.globalAlpha;i.font=j.font;i.textAlign=j.textAlign;i.textBaseline=j.textBaseline;i.arcScaleX_=j.arcScaleX_;i.arcScaleY_=j.arcScaleY_;i.lineScale_=j.lineScale_}var b={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",grey:"#808080",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",oldlace:"#FDF5E6",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",whitesmoke:"#F5F5F5",yellowgreen:"#9ACD32"};function M(j){var p=j.indexOf("(",3);var i=j.indexOf(")",p+1);var m=j.substring(p+1,i).split(",");if(m.length!=4||j.charAt(3)!="a"){m[3]=1}return m}function c(i){return parseFloat(i)/100}function r(j,m,i){return Math.min(i,Math.max(m,j))}function I(ag){var i,ai,aj,ah,ak,Z;ah=parseFloat(ag[0])/360%360;if(ah<0){ah++}ak=r(c(ag[1]),0,1);Z=r(c(ag[2]),0,1);if(ak==0){i=ai=aj=Z}else{var j=Z<0.5?Z*(1+ak):Z+ak-Z*ak;var m=2*Z-j;i=a(m,j,ah+1/3);ai=a(m,j,ah);aj=a(m,j,ah-1/3)}return"#"+k[Math.floor(i*255)]+k[Math.floor(ai*255)]+k[Math.floor(aj*255)]}function a(j,i,m){if(m<0){m++}if(m>1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}var C={};function F(j){if(j in C){return C[j]}var ag,Z=1;j=String(j);if(j.charAt(0)=="#"){ag=j}else{if(/^rgb/.test(j)){var p=M(j);var ag="#",ah;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){ah=Math.floor(c(p[m])*255)}else{ah=+p[m]}ag+=k[r(ah,0,255)]}Z=+p[3]}else{if(/^hsl/.test(j)){var p=M(j);ag=I(p);Z=p[3]}else{ag=b[j]||j}}}return C[j]={color:ag,alpha:Z}}var o={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||o.style,variant:m.fontVariant||o.variant,weight:m.fontWeight||o.weight,size:m.fontSize||o.size,family:m.fontFamily||o.family}}function u(m,j){var i={};for(var ah in m){i[ah]=m[ah]}var ag=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ag*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ag/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=Z/0.75}else{i.size=ag}}}}}i.size*=0.981;return i}function ac(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}var s={butt:"flat",round:"round"};function S(i){return s[i]||"square"}function D(i){this.m_=B();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=d*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var m="width:"+i.clientWidth+"px;height:"+i.clientHeight+"px;overflow:hidden;position:absolute";var j=i.ownerDocument.createElement("div");j.style.cssText=m;i.appendChild(j);var p=j.cloneNode(false);p.style.backgroundColor="red";p.style.filter="alpha(opacity=0)";i.appendChild(p);this.element_=j;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var q=D.prototype;q.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.lineTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.bezierCurveTo=function(m,j,ak,aj,ai,ag){var i=V(this,ai,ag);var ah=V(this,m,j);var Z=V(this,ak,aj);K(this,ah,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}q.quadraticCurveTo=function(ai,m,j,i){var ah=V(this,ai,m);var ag=V(this,j,i);var aj={x:this.currentX_+2/3*(ah.x-this.currentX_),y:this.currentY_+2/3*(ah.y-this.currentY_)};var Z={x:aj.x+(ag.x-this.currentX_)/3,y:aj.y+(ag.y-this.currentY_)/3};K(this,aj,Z,ag)};q.arc=function(al,aj,ak,ag,j,m){ak*=d;var ap=m?"at":"wa";var am=al+A(ag)*ak-f;var ao=aj+l(ag)*ak-f;var i=al+A(j)*ak-f;var an=aj+l(j)*ak-f;if(am==i&&!m){am+=0.125}var Z=V(this,al,aj);var ai=V(this,am,ao);var ah=V(this,i,an);this.currentPath_.push({type:ap,x:Z.x,y:Z.y,radius:ak,xStart:ai.x,yStart:ai.y,xEnd:ah.x,yEnd:ah.y})};q.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};q.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};q.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};q.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};q.createRadialGradient=function(p,ag,m,j,Z,i){var ah=new U("gradientradial");ah.x0_=p;ah.y0_=ag;ah.r0_=m;ah.x1_=j;ah.y1_=Z;ah.r1_=i;return ah};q.drawImage=function(aq,m){var aj,ah,al,ay,ao,am,at,aA;var ak=aq.runtimeStyle.width;var ap=aq.runtimeStyle.height;aq.runtimeStyle.width="auto";aq.runtimeStyle.height="auto";var ai=aq.width;var aw=aq.height;aq.runtimeStyle.width=ak;aq.runtimeStyle.height=ap;if(arguments.length==3){aj=arguments[1];ah=arguments[2];ao=am=0;at=al=ai;aA=ay=aw}else{if(arguments.length==5){aj=arguments[1];ah=arguments[2];al=arguments[3];ay=arguments[4];ao=am=0;at=ai;aA=aw}else{if(arguments.length==9){ao=arguments[1];am=arguments[2];at=arguments[3];aA=arguments[4];aj=arguments[5];ah=arguments[6];al=arguments[7];ay=arguments[8]}else{throw Error("Invalid number of arguments")}}}var az=V(this,aj,ah);var p=at/2;var j=aA/2;var ax=[];var i=10;var ag=10;ax.push(" <g_vml_:group",' coordsize="',d*i,",",d*ag,'"',' coordorigin="0,0"',' style="width:',i,"px;height:",ag,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]||this.m_[1][1]!=1||this.m_[1][0]){var Z=[];Z.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",n(az.x/d),",","Dy=",n(az.y/d),"");var av=az;var au=V(this,aj+al,ah);var ar=V(this,aj,ah+ay);var an=V(this,aj+al,ah+ay);av.x=ab.max(av.x,au.x,ar.x,an.x);av.y=ab.max(av.y,au.y,ar.y,an.y);ax.push("padding:0 ",n(av.x/d),"px ",n(av.y/d),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",Z.join(""),", sizingmethod='clip');")}else{ax.push("top:",n(az.y/d),"px;left:",n(az.x/d),"px;")}ax.push(' ">','<g_vml_:image src="',aq.src,'"',' style="width:',d*al,"px;"," height:",d*ay,'px"',' cropleft="',ao/ai,'"',' croptop="',am/aw,'"',' cropright="',(ai-ao-at)/ai,'"',' cropbottom="',(aw-am-aA)/aw,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",ax.join(""))};q.stroke=function(ao){var Z=10;var ap=10;var ag=5000;var ai={x:null,y:null};var an={x:null,y:null};for(var aj=0;aj<this.currentPath_.length;aj+=ag){var am=[];var ah=false;am.push("<g_vml_:shape",' filled="',!!ao,'"',' style="position:absolute;width:',Z,"px;height:",ap,'px;"',' coordorigin="0,0"',' coordsize="',d*Z,",",d*ap,'"',' stroked="',!ao,'"',' path="');var aq=false;for(var ak=aj;ak<Math.min(aj+ag,this.currentPath_.length);ak++){if(ak%ag==0&&ak>0){am.push(" m ",n(this.currentPath_[ak-1].x),",",n(this.currentPath_[ak-1].y))}var m=this.currentPath_[ak];var al;switch(m.type){case"moveTo":al=m;am.push(" m ",n(m.x),",",n(m.y));break;case"lineTo":am.push(" l ",n(m.x),",",n(m.y));break;case"close":am.push(" x ");m=null;break;case"bezierCurveTo":am.push(" c ",n(m.cp1x),",",n(m.cp1y),",",n(m.cp2x),",",n(m.cp2y),",",n(m.x),",",n(m.y));break;case"at":case"wa":am.push(" ",m.type," ",n(m.x-this.arcScaleX_*m.radius),",",n(m.y-this.arcScaleY_*m.radius)," ",n(m.x+this.arcScaleX_*m.radius),",",n(m.y+this.arcScaleY_*m.radius)," ",n(m.xStart),",",n(m.yStart)," ",n(m.xEnd),",",n(m.yEnd));break}if(m){if(ai.x==null||m.x<ai.x){ai.x=m.x}if(an.x==null||m.x>an.x){an.x=m.x}if(ai.y==null||m.y<ai.y){ai.y=m.y}if(an.y==null||m.y>an.y){an.y=m.y}}}am.push(' ">');if(!ao){w(this,am)}else{G(this,am,ai,an)}am.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",am.join(""))}};function w(m,ag){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ag.push("<g_vml_:stroke",' opacity="',Z,'"',' joinstyle="',m.lineJoin,'"',' miterlimit="',m.miterLimit,'"',' endcap="',S(m.lineCap),'"',' weight="',i,'px"',' color="',p,'" />')}function G(aq,ai,aK,ar){var aj=aq.fillStyle;var aB=aq.arcScaleX_;var aA=aq.arcScaleY_;var j=ar.x-aK.x;var p=ar.y-aK.y;if(aj instanceof U){var an=0;var aF={x:0,y:0};var ax=0;var am=1;if(aj.type_=="gradient"){var al=aj.x0_/aB;var m=aj.y0_/aA;var ak=aj.x1_/aB;var aM=aj.y1_/aA;var aJ=V(aq,al,m);var aI=V(aq,ak,aM);var ag=aI.x-aJ.x;var Z=aI.y-aJ.y;an=Math.atan2(ag,Z)*180/Math.PI;if(an<0){an+=360}if(an<0.000001){an=0}}else{var aJ=V(aq,aj.x0_,aj.y0_);aF={x:(aJ.x-aK.x)/j,y:(aJ.y-aK.y)/p};j/=aB*d;p/=aA*d;var aD=ab.max(j,p);ax=2*aj.r0_/aD;am=2*aj.r1_/aD-ax}var av=aj.colors_;av.sort(function(aN,i){return aN.offset-i.offset});var ap=av.length;var au=av[0].color;var at=av[ap-1].color;var az=av[0].alpha*aq.globalAlpha;var ay=av[ap-1].alpha*aq.globalAlpha;var aE=[];for(var aH=0;aH<ap;aH++){var ao=av[aH];aE.push(ao.offset*am+ax+" "+ao.color)}ai.push('<g_vml_:fill type="',aj.type_,'"',' method="none" focus="100%"',' color="',au,'"',' color2="',at,'"',' colors="',aE.join(","),'"',' opacity="',ay,'"',' g_o_:opacity2="',az,'"',' angle="',an,'"',' focusposition="',aF.x,",",aF.y,'" />')}else{if(aj instanceof T){if(j&&p){var ah=-aK.x;var aC=-aK.y;ai.push("<g_vml_:fill",' position="',ah/j*aB*aB,",",aC/p*aA*aA,'"',' type="tile"',' src="',aj.src_,'" />')}}else{var aL=F(aq.fillStyle);var aw=aL.color;var aG=aL.alpha*aq.globalAlpha;ai.push('<g_vml_:fill color="',aw,'" opacity="',aG,'" />')}}}q.fill=function(){this.stroke(true)};q.closePath=function(){this.currentPath_.push({type:"close"})};function V(j,Z,p){var i=j.m_;return{x:d*(Z*i[0][0]+p*i[1][0]+i[2][0])-f,y:d*(Z*i[0][1]+p*i[1][1]+i[2][1])-f}}q.save=function(){var i={};v(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(B(),this.m_)};q.restore=function(){if(this.aStack_.length){v(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function h(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function aa(j,i,p){if(!h(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}q.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];aa(this,J(i,this.m_),false)};q.rotate=function(j){var p=A(j);var m=l(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];aa(this,J(i,this.m_),false)};q.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];aa(this,J(i,this.m_),true)};q.transform=function(Z,p,ah,ag,j,i){var m=[[Z,p,0],[ah,ag,0],[j,i,1]];aa(this,J(m,this.m_),true)};q.setTransform=function(ag,Z,ai,ah,p,j){var i=[[ag,Z,0],[ai,ah,0],[p,j,1]];aa(this,i,true)};q.drawText_=function(am,ak,aj,ap,ai){var ao=this.m_,at=1000,j=0,ar=at,ah={x:0,y:0},ag=[];var i=u(E(this.font),this.element_);var p=ac(i);var au=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=au.direction=="ltr"?"right":"left";break;case"start":Z=au.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":ah.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":ah.y=-i.size/2.25;break}switch(Z){case"right":j=at;ar=0.05;break;case"center":j=ar=at/2;break}var aq=V(this,ak+ah.x,aj+ah.y);ag.push('<g_vml_:line from="',-j,' 0" to="',ar,' 0.05" ',' coordsize="100 100" coordorigin="0 0"',' filled="',!ai,'" stroked="',!!ai,'" style="position:absolute;width:1px;height:1px;">');if(ai){w(this,ag)}else{G(this,ag,{x:-j,y:0},{x:ar,y:i.size})}var an=ao[0][0].toFixed(3)+","+ao[1][0].toFixed(3)+","+ao[0][1].toFixed(3)+","+ao[1][1].toFixed(3)+",0,0";var al=n(aq.x/d)+","+n(aq.y/d);ag.push('<g_vml_:skew on="t" matrix="',an,'" ',' offset="',al,'" origin="',j,' 0" />','<g_vml_:path textpathok="true" />','<g_vml_:textpath on="true" string="',af(am),'" style="v-text-align:',Z,";font:",af(p),'" /></g_vml_:line>');this.element_.insertAdjacentHTML("beforeEnd",ag.join(""))};q.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};q.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};q.measureText=function(m){if(!this.textMeasureEl_){var i='<span style="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;"></span>';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};q.clip=function(){};q.arcTo=function(){};q.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var X=P.prototype=new Error;X.INDEX_SIZE_ERR=1;X.DOMSTRING_SIZE_ERR=2;X.HIERARCHY_REQUEST_ERR=3;X.WRONG_DOCUMENT_ERR=4;X.INVALID_CHARACTER_ERR=5;X.NO_DATA_ALLOWED_ERR=6;X.NO_MODIFICATION_ALLOWED_ERR=7;X.NOT_FOUND_ERR=8;X.NOT_SUPPORTED_ERR=9;X.INUSE_ATTRIBUTE_ERR=10;X.INVALID_STATE_ERR=11;X.SYNTAX_ERR=12;X.INVALID_MODIFICATION_ERR=13;X.NAMESPACE_ERR=14;X.INVALID_ACCESS_ERR=15;X.VALIDATION_ERR=16;X.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=e;CanvasRenderingContext2D=D;CanvasGradient=U;CanvasPattern=T;DOMException=P})()};
\ No newline at end of file
diff --git a/testing/CansWebInterface/static/exp-menu.css b/testing/CansWebInterface/static/exp-menu.css
new file mode 100644 (file)
index 0000000..6d1cbd2
--- /dev/null
@@ -0,0 +1,89 @@
+/* 
+    Document   : nav-menu
+    Created on : 21/09/2013, 8:33:30 PM
+    Author     : Jeremy Tan Ammended By James Rosher
+    Description:
+        Navigation menu styling.
+        Background colour: #2A2A2A
+        Highlight colour: #f2f2f2
+        Normal text colour: #bbbbbb
+*/
+
+/** Easing **/
+.exp-menu {
+  transition: all 0.2s ease 0s;
+}
+
+/** Main menu list styling **/
+.exp-menu ul {
+  list-style: none outside none;
+  margin: 0;
+  padding-left: 0;
+  text-align: center;
+}
+
+/** Menu item styling **/
+.exp-menu li {
+  display: inline-block;
+  position: relative;
+}
+
+/** Link styling **/
+.exp-menu a {
+  background-color: #3090C7;
+  border-radius: 2px;
+  border: 1px solid #F0FFFF;
+  color: #FFFFFF;
+  display: inline-block;
+  margin: 0.1em;
+  padding: 0.2em 2em;
+  text-decoration: none;
+  transition: all 0.2s ease 0s;
+}
+
+/** Highlight currently hovered-over item **/
+.exp-menu li:hover > a {
+  background-color: #87AFC7;
+  color: #2a2a2a;
+}
+
+/** Display submenu when hovering over list item **/
+.exp-menu ul li:hover > ul {
+  display: block;
+}
+
+/** Submenu styling **/
+.exp-menu ul ul {
+  background-color: #2A2A2A;
+  border-radius: 2px;
+  box-shadow: 1px 1px 1px #2A2A2A;
+  display: none;
+  left: 0;
+  padding: 0.25em 0.5em;
+  position: absolute;
+  text-align: center;
+  z-index: 10;
+}
+
+/** Submenu link styling **/
+.exp-menu ul ul a {
+  width: 11em;
+  padding: 0.25em 0.5em;
+}
+
+/**
+  Style of menu:
+  <div class="exp-menu">
+    <ul class="menu">
+      <li><a href="#">Link 1</a></li>
+      <li><a href="#">Link 2</a></li>
+      <li>
+        <a href="#">Link 3</a>
+        <ul class="submenu">
+          <li><a href="#">Sub-link 1</a></li>
+          <li><a href="#">Sub-link 2</a></li>
+        </ul>
+      </li>
+    </ul>
+  </div>
+*/
\ No newline at end of file
diff --git a/testing/CansWebInterface/static/jquery-1.10.1.min.js b/testing/CansWebInterface/static/jquery-1.10.1.min.js
new file mode 100644 (file)
index 0000000..e407e76
--- /dev/null
@@ -0,0 +1,6 @@
+/*! jQuery v1.10.1 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery-1.10.1.min.map
+*/
+(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.1",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=lt(),k=lt(),E=lt(),S=!1,A=function(){return 0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=bt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+xt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return At(e.replace(z,"$1"),t,n,i)}function st(e){return K.test(e+"")}function lt(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function ut(e){return e[b]=!0,e}function ct(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function pt(e,t,n){e=e.split("|");var r,i=e.length,a=n?null:t;while(i--)(r=o.attrHandle[e[i]])&&r!==t||(o.attrHandle[e[i]]=a)}function ft(e,t){var n=e.getAttributeNode(t);return n&&n.specified?n.value:e[t]===!0?t.toLowerCase():null}function dt(e,t){return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function ht(e){return"input"===e.nodeName.toLowerCase()?e.defaultValue:t}function gt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function mt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function yt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function vt(e){return ut(function(t){return t=+t,ut(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.parentWindow;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.frameElement&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ct(function(e){return e.innerHTML="<a href='#'></a>",pt("type|href|height|width",dt,"#"===e.firstChild.getAttribute("href")),pt(B,ft,null==e.getAttribute("disabled")),e.className="i",!e.getAttribute("className")}),r.input=ct(function(e){return e.innerHTML="<input>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}),pt("value",ht,r.attributes&&r.input),r.getElementsByTagName=ct(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ct(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ct(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=st(n.querySelectorAll))&&(ct(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ct(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=st(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ct(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=st(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},r.sortDetached=ct(function(e){return 1&e.compareDocumentPosition(n.createElement("div"))}),A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return gt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?gt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:ut,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=bt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?ut(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ut(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?ut(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ut(function(e){return function(t){return at(e,t).length>0}}),contains:ut(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:ut(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:vt(function(){return[0]}),last:vt(function(e,t){return[t-1]}),eq:vt(function(e,t,n){return[0>n?n+t:n]}),even:vt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:vt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:vt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:vt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=mt(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=yt(n);function bt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function xt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function wt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function Tt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function Ct(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function Nt(e,t,n,r,i,o){return r&&!r[b]&&(r=Nt(r)),i&&!i[b]&&(i=Nt(i,o)),ut(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||St(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:Ct(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=Ct(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=Ct(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function kt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=wt(function(e){return e===t},s,!0),p=wt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[wt(Tt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return Nt(l>1&&Tt(f),l>1&&xt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&kt(e.slice(l,r)),i>r&&kt(e=e.slice(r)),i>r&&xt(e))}f.push(n)}return Tt(f)}function Et(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=Ct(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?ut(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=bt(e)),n=t.length;while(n--)o=kt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Et(i,r))}return o};function St(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function At(e,t,n,i){var a,s,u,c,p,f=bt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&xt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}o.pseudos.nth=o.pseudos.eq;function jt(){}jt.prototype=o.filters=o.pseudos,o.setFilters=new jt,r.sortStable=b.split("").sort(A).join("")===b,p(),[0,0].sort(A),r.detectDuplicates=S,x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!l||i&&!u||(n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)
+}),n=s=l=u=r=o=null,t}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=x(this),l=t,u=e.match(T)||[];while(o=u[a++])l=r?l:!s.hasClass(o),s[l?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle);
+u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
diff --git a/testing/CansWebInterface/static/jquery.maphilight.min.js b/testing/CansWebInterface/static/jquery.maphilight.min.js
new file mode 100644 (file)
index 0000000..ec9d021
--- /dev/null
@@ -0,0 +1 @@
+(function(G){var B,J,C,K,N,M,I,E,H,A,L;J=!!document.createElement("canvas").getContext;B=(function(){var P=document.createElement("div");P.innerHTML='<v:shape id="vml_flag1" adj="1" />';var O=P.firstChild;O.style.behavior="url(#default#VML)";return O?typeof O.adj=="object":true})();if(!(J||B)){G.fn.maphilight=function(){return this};return }if(J){E=function(O){return Math.max(0,Math.min(parseInt(O,16),255))};H=function(O,P){return"rgba("+E(O.substr(0,2))+","+E(O.substr(2,2))+","+E(O.substr(4,2))+","+P+")"};C=function(O){var P=G('<canvas style="width:'+O.width+"px;height:"+O.height+'px;"></canvas>').get(0);P.getContext("2d").clearRect(0,0,P.width,P.height);return P};var F=function(Q,O,R,P,S){P=P||0;S=S||0;Q.beginPath();if(O=="rect"){Q.rect(R[0]+P,R[1]+S,R[2]-R[0],R[3]-R[1])}else{if(O=="poly"){Q.moveTo(R[0]+P,R[1]+S);for(i=2;i<R.length;i+=2){Q.lineTo(R[i]+P,R[i+1]+S)}}else{if(O=="circ"){Q.arc(R[0]+P,R[1]+S,R[2],0,Math.PI*2,false)}}}Q.closePath()};K=function(Q,T,U,X,O){var S,P=Q.getContext("2d");if(X.shadow){P.save();if(X.shadowPosition=="inside"){F(P,T,U);P.clip()}var R=Q.width*100;var W=Q.height*100;F(P,T,U,R,W);P.shadowOffsetX=X.shadowX-R;P.shadowOffsetY=X.shadowY-W;P.shadowBlur=X.shadowRadius;P.shadowColor=H(X.shadowColor,X.shadowOpacity);var V=X.shadowFrom;if(!V){if(X.shadowPosition=="outside"){V="fill"}else{V="stroke"}}if(V=="stroke"){P.strokeStyle="rgba(0,0,0,1)";P.stroke()}else{if(V=="fill"){P.fillStyle="rgba(0,0,0,1)";P.fill()}}P.restore();if(X.shadowPosition=="outside"){P.save();F(P,T,U);P.globalCompositeOperation="destination-out";P.fillStyle="rgba(0,0,0,1);";P.fill();P.restore()}}P.save();F(P,T,U);if(X.fill){P.fillStyle=H(X.fillColor,X.fillOpacity);P.fill()}if(X.stroke){P.strokeStyle=H(X.strokeColor,X.strokeOpacity);P.lineWidth=X.strokeWidth;P.stroke()}P.restore();if(X.fade){G(Q).css("opacity",0).animate({opacity:1},100)}};N=function(O){O.getContext("2d").clearRect(0,0,O.width,O.height)}}else{C=function(O){return G('<var style="zoom:1;overflow:hidden;display:block;width:'+O.width+"px;height:"+O.height+'px;"></var>').get(0)};K=function(P,T,U,X,O){var V,W,R,S;for(var Q in U){U[Q]=parseInt(U[Q],10)}V='<v:fill color="#'+X.fillColor+'" opacity="'+(X.fill?X.fillOpacity:0)+'" />';W=(X.stroke?'strokeweight="'+X.strokeWidth+'" stroked="t" strokecolor="#'+X.strokeColor+'"':'stroked="f"');R='<v:stroke opacity="'+X.strokeOpacity+'"/>';if(T=="rect"){S=G('<v:rect name="'+O+'" filled="t" '+W+' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:'+U[0]+"px;top:"+U[1]+"px;width:"+(U[2]-U[0])+"px;height:"+(U[3]-U[1])+'px;"></v:rect>')}else{if(T=="poly"){S=G('<v:shape name="'+O+'" filled="t" '+W+' coordorigin="0,0" coordsize="'+P.width+","+P.height+'" path="m '+U[0]+","+U[1]+" l "+U.join(",")+' x e" style="zoom:1;margin:0;padding:0;display:block;position:absolute;top:0px;left:0px;width:'+P.width+"px;height:"+P.height+'px;"></v:shape>')}else{if(T=="circ"){S=G('<v:oval name="'+O+'" filled="t" '+W+' style="zoom:1;margin:0;padding:0;display:block;position:absolute;left:'+(U[0]-U[2])+"px;top:"+(U[1]-U[2])+"px;width:"+(U[2]*2)+"px;height:"+(U[2]*2)+'px;"></v:oval>')}}}S.get(0).innerHTML=V+R;G(P).append(S)};N=function(P){var O=G("<div>"+P.innerHTML+"</div>");O.children("[name=highlighted]").remove();P.innerHTML=O.html()}}M=function(P){var O,Q=P.getAttribute("coords").split(",");for(O=0;O<Q.length;O++){Q[O]=parseFloat(Q[O])}return[P.getAttribute("shape").toLowerCase().substr(0,4),Q]};L=function(Q,P){var O=G(Q);return G.extend({},P,G.metadata?O.metadata():false,O.data("maphilight"))};A=function(O){if(!O.complete){return false}if(typeof O.naturalWidth!="undefined"&&O.naturalWidth===0){return false}return true};I={position:"absolute",left:0,top:0,padding:0,border:0};var D=false;G.fn.maphilight=function(O){O=G.extend({},G.fn.maphilight.defaults,O);if(!J&&!D){G(window).ready(function(){document.namespaces.add("v","urn:schemas-microsoft-com:vml");var Q=document.createStyleSheet();var P=["shape","rect","oval","circ","fill","stroke","imagedata","group","textbox"];G.each(P,function(){Q.addRule("v\\:"+this,"behavior: url(#default#VML); antialias:true")})});D=true}return this.each(function(){var U,R,Y,Q,T,V,X,S,W;U=G(this);if(!A(this)){return window.setTimeout(function(){U.maphilight(O)},200)}Y=G.extend({},O,G.metadata?U.metadata():false,U.data("maphilight"));W=U.get(0).getAttribute("usemap");if(!W){return }Q=G('map[name="'+W.substr(1)+'"]');if(!(U.is('img,input[type="image"]')&&W&&Q.size()>0)){return }if(U.hasClass("maphilighted")){var P=U.parent();U.insertBefore(P);P.remove();G(Q).unbind(".maphilight").find("area[coords]").unbind(".maphilight")}R=G("<div></div>").css({display:"block",background:'url("'+this.src+'")',position:"relative",padding:0,width:this.width,height:this.height});if(Y.wrapClass){if(Y.wrapClass===true){R.addClass(G(this).attr("class"))}else{R.addClass(Y.wrapClass)}}U.before(R).css("opacity",0).css(I).remove();if(B){U.css("filter","Alpha(opacity=0)")}R.append(U);T=C(this);G(T).css(I);T.height=this.height;T.width=this.width;X=function(c){var a,b;b=L(this,Y);if(!b.neverOn&&!b.alwaysOn){a=M(this);K(T,a[0],a[1],b,"highlighted");if(b.groupBy){var Z;if(/^[a-zA-Z][\-a-zA-Z]+$/.test(b.groupBy)){Z=Q.find("area["+b.groupBy+'="'+G(this).attr(b.groupBy)+'"]')}else{Z=Q.find(b.groupBy)}var d=this;Z.each(function(){if(this!=d){var f=L(this,Y);if(!f.neverOn&&!f.alwaysOn){var e=M(this);K(T,e[0],e[1],f,"highlighted")}}})}if(!J){G(T).append("<v:rect></v:rect>")}}};G(Q).bind("alwaysOn.maphilight",function(){if(V){N(V)}if(!J){G(T).empty()}G(Q).find("area[coords]").each(function(){var Z,a;a=L(this,Y);if(a.alwaysOn){if(!V&&J){V=C(U[0]);G(V).css(I);V.width=U[0].width;V.height=U[0].height;U.before(V)}a.fade=a.alwaysOnFade;Z=M(this);if(J){K(V,Z[0],Z[1],a,"")}else{K(T,Z[0],Z[1],a,"")}}})});G(Q).trigger("alwaysOn.maphilight").find("area[coords]").bind("mouseover.maphilight",X).bind("mouseout.maphilight",function(Z){N(T)});U.before(T);U.addClass("maphilighted")})};G.fn.maphilight.defaults={fill:true,fillColor:"000000",fillOpacity:0.2,stroke:true,strokeColor:"ff0000",strokeOpacity:1,strokeWidth:1,fade:true,alwaysOn:false,neverOn:false,groupBy:false,wrapClass:true,shadow:false,shadowX:0,shadowY:0,shadowRadius:6,shadowColor:"000000",shadowOpacity:0.8,shadowPosition:"outside",shadowFrom:false}})(jQuery);
\ No newline at end of file
diff --git a/testing/CansWebInterface/static/mctx.control.js b/testing/CansWebInterface/static/mctx.control.js
new file mode 100644 (file)
index 0000000..24b0448
--- /dev/null
@@ -0,0 +1,166 @@
+/**
+ * Code for the controls page.
+ * @date 19-10-2013
+ */
+
+mctx.control = {};
+mctx.control.api = mctx.api + 'control'
+mctx.control.states = {
+  start : 0,
+  pause : 1,
+  resume : 2,
+  stop : 3,
+  emergency : 4
+};
+mctx.control.state = null;
+
+function toggleControls(running) {
+  if (running) {
+    $("#experiment-stop").show();
+    $("#pressure-widget").show();
+    $("#start-widget").hide();
+  } else {
+    $("#start-widget").show();
+    $("#experiment-stop").hide();
+    $("#pressure-widget").hide();
+  }
+}
+
+$.fn.setStatusUpdater = function () {
+  var result = this;
+  
+  var updater = function () {
+    $.ajax({
+      url : mctx.control.api,
+      data : {'action' : 'identify'}
+    }).done(function (data) {
+      if (!result.checkStatus(data)) {
+        $(result).parent().addClass("fail");
+        setTimeout(updater, 4000);
+        return;
+      }
+
+      var text;
+      var running = false;
+      var fail = false;
+      switch (data.control_state_id) {
+        case mctx.control.states.start:
+          text = "Experiment started - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+        break;
+        case mctx.control.states.pause:
+          text = "Experiment paused - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+        break;
+        case mctx.control.states.stop:
+          text = "No experiment running.";
+        break;
+        case mctx.control.states.emergency:
+          text = "Emergency mode - '" + data.control_experiment_name +
+                 "' by " + data.control_user_name;
+          running = true;
+          fail = true;
+        default:
+          text = "Unknown mode: " + data.control_state_id;
+          fail = true;
+      }
+      
+      if (data.control_state_id !== mctx.control.state) {      
+        toggleControls(running);
+        $(result).text(text);
+        if (fail) {
+          $(result).parent().addClass("fail");
+        } else {
+          $(result).parent().addClass("pass");
+        }
+        
+        mctx.control.state = data.control_state_id;
+      }
+      
+      setTimeout(updater, 2000);
+    })
+   .fail(function () {
+     $(result).text("Connection failed.").parent().addClass("fail");
+     setTimeout(updater, 4000);
+   });
+  };
+  
+  updater();
+};
+
+
+$.fn.startExperiment = function (group, experiment, force, result) {
+ $(group).attr('disabled', 'disabled');
+ if (!experiment || !experiment.match(/^[a-zA-Z0-9_-]+$/)) {
+   result.text("Experiment names must be composed of alphanumeric characters" + 
+               " or the characters -_-").addClass("fail");
+   $(group).removeAttr('disabled');
+   return;
+ } 
+ var data = {action : "start", name : experiment};
+ if (force) {
+   data.force = 1;
+ }
+ $.ajax({
+   url : mctx.control.api,
+   data : data
+ }).done(function (data) {
+   if (!result.checkStatus(data)) {
+     return;
+   }
+   result.html("&nbsp;");
+   toggleControls(true);
+ }).always(function () {
+   $(group).removeAttr('disabled');
+ });
+};
+
+$.fn.stopExperiment = function (result) {
+  var stop = this;
+  stop.attr('disabled', 'disabled');
+  result.text("Stopping the experiment...");
+  
+  $.ajax({
+    url : mctx.control.api,
+    data : {action : "stop"}
+  }).done(function (data) {
+    if (!result.checkStatus(data)) {
+      return;
+    }
+    result.html("&nbsp;");
+    toggleControls(false);
+  }).always(function () {
+    stop.removeAttr('disabled');
+  });
+};
+
+$.fn.setPressure = function(pressure, result) {
+  result.html("&nbsp;");
+  
+  for (var k in pressure) {
+    var n = Number(pressure[k]);
+    if (isNaN(n) || n < 0) {
+      result.text("You must give positive numeric values.").addClass("fail");
+      return;
+    }
+    pressure[k] = n;
+  }
+  
+  var set = pressure['set'] + "," + pressure['wait'] + ","
+            pressure['size'] + "," + pressure['count'];
+  $.ajax({
+    url : mctx.api + "actuators",
+    data : {id : mctx.actuator.pressure_regulator, set : set}
+  }).done(function (data) {
+    if (!result.checkStatus(data)) {
+      return;
+    }
+    
+    result.text("Set ok!").removeClass("fail").addClass("pass");
+  });
+};
\ No newline at end of file
diff --git a/testing/CansWebInterface/static/mctx.gui.js b/testing/CansWebInterface/static/mctx.gui.js
new file mode 100644 (file)
index 0000000..3df2bac
--- /dev/null
@@ -0,0 +1,346 @@
+/**
+* MCTX3420 2013 GUI stuff.
+* Coding style:
+*  - Always end statements with semicolons
+*  - Egyptian brackets are highly recommended (*cough*).
+*  - Don't use synchronous stuff - hook events into callbacks
+*  - $.fn functions should return either themselves or some useful object
+*    to allow for chaining of method calls
+*/
+
+mctx = {};
+//Don't use this in the final version
+mctx.location = window.location.pathname;
+mctx.location = mctx.location.substring(0, mctx.location.lastIndexOf('/')) + "/";
+//mctx.location = location.protocol + "//" + location.host + "/";
+mctx.api = location.protocol + "//" + location.host + "/" + "api/";
+mctx.expected_api_version = 0;
+mctx.has_control = false;
+mctx.debug = true;
+
+mctx.menu = [
+    {'text' : 'Home', href : mctx.location + 'index.html'},
+    {'text' : 'Experiment control', href : mctx.location + 'control.html'},
+    {'text' : 'Pin debugging', href : mctx.location + 'pintest.html'},
+    {'text' : 'Help', href : mctx.location + 'help.html'}
+];
+
+mctx.status = {
+    OK : 1,
+    ERROR : -1,
+    UNAUTHORIZED : -2,
+    NOTRUNNING : -3,
+    ALREADYEXISTS : -4
+};
+
+mctx.statusCodesDescription = {
+    "1" : "Ok",
+    "-1" : "General error",
+    "-2" : "Unauthorized",
+    "-3" : "Not running",
+    "-4" : "Already exists"
+};
+
+mctx.sensors = {
+    0 : {name : "Strain gauge 1"},
+    1 : {name : "Strain gauge 2"},
+    2 : {name : "Strain gauge 3"},
+    3 : {name : "Strain gauge 4"},
+    4 : {name : "Pressure sensor 1"},
+    5 : {name : "Pressure sensor 2"},
+    6 : {name : "Pressure sensor 3"}
+};
+
+mctx.actuators = {
+    0 : {name : "Solenoid 1"},
+    1 : {name : "Solenoid 2"},
+    2 : {name : "Solenoid 3"},
+    3 : {name : "Pressure regulator"}
+};
+
+mctx.actuator = {};
+mctx.actuator.pressure_regulator = 0;
+
+mctx.strain_gauges = {};
+mctx.strain_gauges.ids = [0, 1, 2, 3];
+mctx.strain_gauges.time_limit = 20;
+
+/**
+* Logs a message if mctx.debug is enabled. This function takes
+* a variable number of arguments and passes them 
+* to alert or console.log (based on browser support).
+* @returns {undefined}
+*/
+function debugLog () {
+    if (mctx.debug) {
+        if (typeof console === "undefined" || typeof console.log === "undefined") {
+            for (var i = 0; i < arguments.length; i++) {
+                alert(arguments[i]);
+            }
+        } else {
+            try {
+              console.log.apply(this, arguments);
+            } catch (e) {
+              //Chromie
+              for (var i = 0; i < arguments.length; i++) {
+                console.log(arguments[i]);
+              }
+            }
+        }
+    }
+}
+
+/**
+* Writes the current date to wherever it's called.
+*/
+function getDate() {
+    document.write((new Date()).toDateString());
+}
+
+/**
+* Should be run before the load of any GUI page.
+* To hook events to be called after this function runs,
+* use the 'always' method, e.g runBeforeLoad().always(function() {my stuff});
+* @param {type} isLoginPage
+* @returns The return value of calling $.ajax
+*/
+function runBeforeLoad(isLoginPage) {
+    return $.ajax({
+        url : mctx.api + "identify"
+    }).done(function (data) {
+        if (data.logged_in && isLoginPage) {
+            if (mctx.debug) {
+                debugLog("Redirect disabled!");
+            } else {
+                window.location = mctx.location;
+            }
+        } else if (!data.logged_in && !isLoginPage) {
+            if (mctx.debug) {
+                debugLog("Redirect disabled!");
+            } else {
+                //Note: this only clears the nameless cookie
+                document.cookie = ""; 
+                window.location = mctx.location + "login.html";
+            }
+        } else {
+            mctx.friendlyName = data.friendly_name;
+        }
+    }).fail(function (jqHXR) {
+        if (mctx.debug) {
+            debugLog("Failed to ident server. Is API running?")
+        } else if (!isLoginPage) {
+            window.location = mctx.location + "login.html";
+        }
+    }).always(function () {
+        
+    });
+}
+
+/**
+ * Populates the navigation menu.
+ */
+$.fn.populateNavMenu = function() {
+    var root = $("<ul/>")
+    for (var i = 0; i < mctx.menu.length; i++) {
+        var item = mctx.menu[i];
+        var entry = $("<li/>").append(
+            $("<a/>", {text : item.text, href: item.href})
+        );
+        root.append(entry);
+    }
+    $(this).append(root);
+    return this;
+}
+
+/**
+* Sets the camera autoupdater
+* Obsolete?
+* @returns {$.fn}
+*/
+$.fn.setCamera = function () {
+    var url = mctx.api + "image";  //http://beaglebone/api/image
+    var update = true;
+
+    //Stop updating if we can't retrieve an image!
+    this.error(function() {
+        update = false;
+    });
+
+    var parent = this;
+
+    var updater = function() {
+        if (!update) {
+            alert("Cam fail");
+            parent.attr("src", "");
+            return;
+        }
+        
+        parent.attr("src", url + "#" + (new Date()).getTime());
+        
+        setTimeout(updater, 10000);
+    };
+
+    updater();
+    return this;
+};
+
+/**
+* Sets the strain graphs to graph stuff. Obsolete?
+* @returns {$.fn}
+*/
+$.fn.setStrainGraphs = function () {
+    var sensor_url = mctx.api + "sensors";
+    var graphdiv = this;
+
+    var updater = function () {
+        var time_limit = mctx.strain_gauges.time_limit;
+        var responses = new Array(mctx.strain_gauges.ids.length);
+        
+        for (var i = 0; i < mctx.strain_gauges.ids.length; i++) {
+            var parameters = {id : i, start_time: -time_limit};
+            responses[i] = $.ajax({url : sensor_url, data : parameters});
+        }
+        
+        $.when.apply(this, responses).then(function () {
+            var data = new Array(arguments.length);
+            for (var i = 0; i < arguments.length; i++) {
+                var raw_data = arguments[i][0].data;
+                var pruned_data = [];
+                var step = ~~(raw_data.length/100);
+                for (var j = 0; j < raw_data.length; j += step)
+                pruned_data.push(raw_data[j]); 
+                data[i] = pruned_data;
+            }
+            $.plot(graphdiv, data);
+            setTimeout(updater, 1000);
+        }, function () {debugLog("It crashed");});
+    };
+
+    updater();
+    return this;
+};
+
+/**
+* Performs a login attempt.
+* @returns The AJAX object of the login request */
+$.fn.login = function () {
+    var username = this.find("input[name='username']").val();
+    var password = this.find("input[name='pass']").val();
+    var out = this.find("#result");
+    var redirect = function () {
+        window.location.href = mctx.location;
+    };
+
+    out.removeAttr("class");
+    out.text("Logging in...");
+
+    return $.ajax({
+        url : mctx.api + "bind",
+        data : {user: username, pass : password}
+    }).done(function (data) {
+        if (data.status < 0) {
+            mctx.has_control = false;
+            out.attr("class", "fail");
+            out.text("Login failed: " + data.description);
+        } else {
+            //todo: error check
+            mctx.has_control = true;
+            out.attr("class", "pass");
+            out.text("Login ok!");
+            setTimeout(redirect, 800);      
+        }
+    }).fail(function (jqXHR) {
+        mctx.has_control = false;
+        out.attr("class", "fail");
+        out.text("Login request failed - connection issues.")
+    });
+};
+
+/**
+* Performs a logout request. The nameless cookie is
+* always cleared and the browser redirected to the login page,
+* independent of whether or not logout succeeded.
+* @returns  The AJAX object of the logout request.
+*/
+$.fn.logout = function () {
+    return $.ajax({
+        url : mctx.api + "unbind"
+    }).always(function () {
+        //Note: this only clears the nameless cookie
+        document.cookie = ""; 
+        window.location = mctx.location + "login.html";
+    });
+};
+
+/**
+* Sets the error log to continuously update.
+* @returns itself */
+$.fn.setErrorLog = function () {
+    var url = mctx.api + "errorlog";
+    var outdiv = this;
+
+    if ($(this).length <= 0) {
+      //No error log, so do nothing.
+      return;
+    }
+
+    var updater = function () {
+        $.ajax({url : url}).done(function (data) {
+            outdiv.text(data);
+            outdiv.scrollTop(
+              outdiv[0].scrollHeight - outdiv.height()
+            );
+            setTimeout(updater, 3000);
+        }).fail(function (jqXHR) {
+            if (jqXHR.status === 502 || jqXHR.status === 0) {
+                outdiv.text("Failed to retrieve the error log.");
+            }
+            setTimeout(updater, 10000); //poll at slower rate
+        });
+    };
+
+    updater();
+    return this;
+};
+
+$.fn.checkStatus = function(data) {
+  if (data.status !== mctx.status.OK) {
+    $(this).text(data.description).removeClass("pass").addClass("fail");
+    return false;
+  }
+  return true;
+};
+
+$(document).ready(function () {
+  //Show the content!
+  $("#content").css("display", "block");
+  
+  //Set the welcome bar
+  var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
+  $("#welcome-container").text("Welcome"+ name + "!");
+  $("#logout-container").css("display", "block");
+  //$("#menu-container").populateNavbar();
+
+  $("#logout").click(function () {
+    $("#logout").logout();
+  });
+  
+  //Enable the error log, if present
+  $("#errorlog").setErrorLog();
+  
+  //Enable the hide/show clicks
+  $("#sidebar-hide").click(function () {
+    $("#sidebar").css("display", "none");
+    $("#sidebar-show").css("display", "inherit");
+    return this;
+  });
+
+  $("#sidebar-show").click(function () {
+    $("#sidebar-show").css("display", "none");
+    $("#sidebar").css("display", "inherit");
+    return this;
+  });
+});
+$(document).ajaxError(function (event, jqXHR) {
+    //console.log("AJAX query failed with: " + jqXHR.status + " (" + jqXHR.statusText + ")");
+});
\ No newline at end of file
diff --git a/testing/CansWebInterface/static/nav-menu.css b/testing/CansWebInterface/static/nav-menu.css
new file mode 100644 (file)
index 0000000..b33898c
--- /dev/null
@@ -0,0 +1,89 @@
+/* 
+    Document   : nav-menu
+    Created on : 21/09/2013, 8:33:30 PM
+    Author     : Jeremy Tan Ammended By James Rosher
+    Description:
+        Navigation menu styling.
+        Background colour: #2A2A2A
+        Highlight colour: #f2f2f2
+        Normal text colour: #bbbbbb
+*/
+
+/** Easing **/
+.nav-menu {
+  transition: all 0.2s ease 0s;
+}
+
+/** Main menu list styling **/
+.nav-menu ul {
+  list-style: none outside none;
+  margin: 0;
+  padding-left: 0;
+  text-align: right;
+}
+
+/** Menu item styling **/
+.nav-menu li {
+  display: inline-block;
+  position: relative;
+}
+
+/** Link styling **/
+.nav-menu a {
+  background-color: #3090C7;
+  border-radius: 2px;
+  border: 1px solid #F0FFFF;
+  color: #FFFFFF;
+  display: inline-block;
+  margin: 0.1em;
+  padding: 0.2em 2em;
+  text-decoration: none;
+  transition: all 0.2s ease 0s;
+}
+
+/** Highlight currently hovered-over item **/
+.nav-menu li:hover > a {
+  background-color: #87AFC7;
+  color: #2a2a2a;
+}
+
+/** Display submenu when hovering over list item **/
+.nav-menu ul li:hover > ul {
+  display: block;
+}
+
+/** Submenu styling **/
+.nav-menu ul ul {
+  background-color: #2A2A2A;
+  border-radius: 2px;
+  box-shadow: 1px 1px 1px #2A2A2A;
+  display: none;
+  left: 0;
+  padding: 0.25em 0.5em;
+  position: absolute;
+  text-align: left;
+  z-index: 10;
+}
+
+/** Submenu link styling **/
+.nav-menu ul ul a {
+  width: 11em;
+  padding: 0.25em 0.5em;
+}
+
+/**
+  Style of menu:
+  <div class="nav-menu">
+    <ul class="menu">
+      <li><a href="#">Link 1</a></li>
+      <li><a href="#">Link 2</a></li>
+      <li>
+        <a href="#">Link 3</a>
+        <ul class="submenu">
+          <li><a href="#">Sub-link 1</a></li>
+          <li><a href="#">Sub-link 2</a></li>
+        </ul>
+      </li>
+    </ul>
+  </div>
+*/
\ No newline at end of file
diff --git a/testing/CansWebInterface/static/sbd4.png b/testing/CansWebInterface/static/sbd4.png
new file mode 100644 (file)
index 0000000..04a2688
Binary files /dev/null and b/testing/CansWebInterface/static/sbd4.png differ
diff --git a/testing/CansWebInterface/static/style.css b/testing/CansWebInterface/static/style.css
new file mode 100644 (file)
index 0000000..ebd8b50
--- /dev/null
@@ -0,0 +1,317 @@
+/** Custom fonts **/
+@font-face {
+  font-family: "Open Sans";
+  src: url("OpenSans.ttf"),
+    url("OpenSans.eot");
+}
+
+body {
+  font-family: "Open Sans", "Lucida Grande","Lucida Sans",Verdana,Arial;
+  font-size: 13px;
+  background-color: #F5F5F5;
+  margin: 0;
+  padding: 0;
+}
+
+hr {
+  border: 0;
+  border-bottom: 1px solid gray;
+}
+
+form.controls {
+  background-color: #F9F9F9;
+  border: 1px solid #808080;
+  padding: 1em;
+  margin: 1em auto;
+}
+
+a {
+  color: #1188DD;
+}
+
+a:active {
+  color: #0066BB;
+}
+
+img {
+  border: none;
+}
+
+.pass {
+  color: #7AE309;
+  font-weight: bold;
+}
+
+.fail {
+  color: #E30909;
+  font-weight: bold
+}
+
+div.centre {
+  text-align: center;
+  margin: auto;
+}
+
+.bold {
+  font-weight: bold;
+}
+
+table {
+  border: none;
+}
+
+table.centre {
+  margin: auto;
+  text-align: center;
+}
+
+table.status, table.status tr, table.status td {
+    padding: 0.2em 0.75em;
+}
+
+td {
+  padding: 0 0.5em;
+}
+
+th {
+  padding: inherit;
+  border-bottom: 1px solid gray;
+}
+
+img.centre {
+  display: block;
+  margin: auto;
+}
+
+input[type="button"], input[type="submit"] {
+  background-color: #F5F5F5;
+  border: 1px solid #A2A2A2;
+  border-radius: 3px;
+  box-shadow: 1px 1px 1px #BBBBBB;
+  transition: all 0.13s ease 0s;
+  padding: 0 0.5em;
+  min-width: 55px;
+  margin: 0.4em 0.1em;
+}
+
+input[type="button"]:active, input[type="submit"]:active {
+  background-color: #E8E8E8;
+}
+
+
+input[type="text"], input[type="password"] {
+  width: 100%; /* IE8 width bugfix */
+  border-radius: 3px;
+  border: 1px solid #CCCCCC;
+  font-size: 16px;
+}
+
+input[type="text"]:focus, input[type="password"]:focus {
+  box-shadow: 0 0 2px #BBBBBB;
+}
+
+#header-wrap {
+  background-color: #2a2a2a;
+  border-top: 0.3em solid #1188DD;
+  color: #f2f2f2;
+  margin-bottom: 1em;
+  box-shadow: 0 0 0.5em #444444;
+  transition: all 0.2s ease 0s;
+}
+
+#header {
+  padding: 1.5em 2em;
+  max-width: 1280px;
+  margin: 0 auto;
+}
+
+#header input[type="button"], #header input[type="submit"] {
+  background-color: #808080;
+  border: 1px solid #808080;
+  box-shadow: none;
+  color: #f2f2f2;
+  cursor: pointer;
+}
+
+#header input[type="button"]:hover, #header input[type="submit"]:hover {
+  background-color: #606060;
+  border: 1px solid #606060;
+}
+
+#header #leftnav {
+  float: left;
+  display: table;
+}
+
+#header #leftnav > div, #header #leftnav > span {
+  display: table-cell;
+  vertical-align: middle;
+  padding-left: 1em;  
+}
+
+#header #leftnav a {
+  color: #2a2a2a;
+}
+
+#title {
+  font-size: 30px;
+}
+
+#header #rightnav {
+  float: right;
+  font-size: 15px;
+  padding-top: 0.5em;
+  text-align: right;
+}
+
+#header #rightnav span {
+  margin-left: 1em;
+}
+
+#header #menu-container {
+  margin-right: 1.5em;
+  font-size: 15px;
+  display: inline-block;
+}
+
+#header #logout-container {
+  /* Hide until activated by JavaScript */
+  display: none;
+}
+
+#header #date {
+  font-size: 12px;
+}
+
+#content-wrap {
+  padding: 1em 2em;
+  max-width: 1280px;
+  margin: 1em auto;
+}
+
+#content  {
+  /*display: none;  Enable in non-debug mode*/
+}
+
+#sidebar{
+  float: right;
+  width: 25%;
+}
+
+#sidebar .title {
+  font-size: 20px;
+  font-weight: bold;
+}
+
+.justify {
+  text-align: justify;
+}
+
+#main {
+  overflow: auto;
+  padding-left: 2em;
+  min-width: 400px;
+}
+
+#main .sub-title {
+  font-size: 18px;
+  font-weight: bold;
+  margin-bottom: 0.25em;
+}
+
+.title {
+  font-size: 24px;
+  font-weight: bold;
+  margin-bottom: 0.5em;
+}
+
+.graph {
+  width: 100%;
+  height: 200px;
+}
+
+.widget {
+  background-color: #EBF4FA;
+  margin: 0.25em 0.25em 1.5em;
+  padding: 1em 1.25em;
+  box-shadow: 0 0 3px #CCCCCC;
+  transition: all 0.2s ease 0s;
+  overflow: auto;
+}
+
+.widget:hover {
+  box-shadow: 0 0 0.25em #AAAAAA;
+}
+
+.plot {
+       box-sizing: border-box;
+       width: 850px;
+       height: 450px;
+       padding: 20px 15px 15px 15px;
+       margin: 15px auto 30px auto;
+       border: 1px solid #ddd;
+       background: #fff;
+       background: linear-gradient(#f6f6f6 0, #fff 50px);
+       background: -o-linear-gradient(#f6f6f6 0, #fff 50px);
+       background: -ms-linear-gradient(#f6f6f6 0, #fff 50px);
+       background: -moz-linear-gradient(#f6f6f6 0, #fff 50px);
+       background: -webkit-linear-gradient(#f6f6f6 0, #fff 50px);
+       box-shadow: 0 3px 10px rgba(0,0,0,0.15);
+       -o-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+       -ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+       -moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+       -webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+       font-size:20px;
+}
+
+.widgetPanel {
+  background-color: #B4CFEC;
+  margin: 0.25em 0.25em 1.5em;
+  padding: 1em 1.25em;
+  box-shadow: 0 0 3px #CCCCCC;
+  transition: all 0.2s ease 0s;
+  overflow: auto;
+}
+
+/** Hack **/
+.clear {
+  clear: both;
+}
+
+#errorlog {
+  overflow: auto;
+  max-width: 100%;
+  width: 100%;
+  height: 6em;
+}
+
+/** For login.html **/
+#login-container {
+  margin: 0 auto;
+  width: 400px;
+  padding: 1.8em 0 0;
+  color: #606060;
+  line-height: 1.8em;
+}
+
+#login-container form {
+  padding: 1em 2em;
+}
+
+#login-container input {
+  font-size: 24px;
+}
+
+#login-container label {
+  font-size: 14px;
+}
+
+#login-container input[type=submit] {
+  font-size: inherit;
+  padding: 0.4em 0.8em;
+}
+
+#login-container #result {
+  clear: both;
+}
diff --git a/testing/CansWebInterface/static/uwacrest-text.png b/testing/CansWebInterface/static/uwacrest-text.png
new file mode 100644 (file)
index 0000000..84d44d0
Binary files /dev/null and b/testing/CansWebInterface/static/uwacrest-text.png differ
diff --git a/testing/CansWebInterface/template.html b/testing/CansWebInterface/template.html
new file mode 100644 (file)
index 0000000..925717c
--- /dev/null
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/base64.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+    
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+    <script type="text/javascript">
+      $(document).ready(function () {
+        $("#menu-container").populateNavbar();
+        $("#login").submit(function () {
+          $("#login").login();
+          return false;
+        });
+        
+        $("#main_controls").submit(function () {
+          //Validate!
+          return false;
+        });
+        //$("#cam1").setCamera();
+        //$("#strain-graphs").setStrainGraphs();
+        $("#errorlog").setErrorLog();
+      });
+    </script>
+  </head>
+  
+<body>
+       <div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+            src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Can Web Interface: TEMPLATE</span>
+        </div>
+               <div class="nav-menu">
+                       <ul class="menu">
+                         <li><a href="dashboard.html">Home</a></li>
+                         <li><a href="setup.html">Setup</a></li>
+                         <li><a href="admin.html">Admin</a></li>
+                         <li><a href="help.html">Help</a></li>
+                       </ul>
+               </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>    
+       <!-- End header -->
+    
+    <div id="content">
+      <div id="sidebar">
+        <div class="widgetPanel">
+          <div class="title">DEMO SIDEPANEL</div>
+          <div class="item">
+            This is a Demonstration Panel Only.
+            <hr>
+            This is a Demonstration Panel Only
+          </div>         
+        </div>
+       </div>
+
+       <!-- End sidebar -->
+
+        <div id="main">
+          <div class="widget">
+            <div class="title">TEMPLATE PAGE</div>
+            <p class="justify">This page is a template only</p>
+                       </div>
+                       </div>
+               </div>
+
+
+       <!-- End main content -->
+      
+    </div>
+</body>
+</html>
diff --git a/testing/CansWebInterface/test.html b/testing/CansWebInterface/test.html
new file mode 100644 (file)
index 0000000..736bce4
--- /dev/null
@@ -0,0 +1,3 @@
+<p>
+This is a test file
+</p>
\ No newline at end of file
index 60817cf..4eb3314 100644 (file)
         $(document).ready(function () {
           $("form").submit(function () { //Prevent form submit globally
             return false;
-          })
+          }).trigger("reset"); //Reset forms
+          
+          //Force enable start buttons if previously disabled
+          $("#start-controls input[type='button']").removeAttr("disabled");
           
           //Set the status updated
           $("#state-exp").setStatusUpdater();
           
           //Set the logic for the start controls
-          $("#start-controls").submit(function () {
+          $("#start-controls input[type='button']").click(function () {
             var start = $("#start-controls input[type='button']");
             var force = $("#start-controls input[name='start_force']");
             
             };   
             $(this).setPressure(pressure, $("#pressure-result"));
           });
+          
+          $("#pressure-controls input[name='clear']").click(function () {
+            $("#pressure-controls")[0].reset();
+            $("#pressure-result").text("");
+          });
+          
+          $("#pressure-controls input[name='zero']").click(function () {
+            $("#pressure-controls input[name='clear']").click();
+            $("#pressure-set").val("0");
+            
+            $("#pressure-controls").submit();
+          });
+          
+          $("#pressure-controls input[name='step-it']").click(function () {
+            var pressure = {
+              set : $("#pressure-set").val(), step : "", wait : "", count : ""
+            };
+            
+            $(this).setPressure(pressure, $("#pressure-result")).done(function () {
+              var next = Number($("#pressure-set").val()) + Number($("#pressure-stepsize").val());
+              $("#pressure-set").val(next.toString());
+            });
+            
+          });
+          $("#samplerate-controls").submit(function () {
+            setSampleRate($("#sensor-select option:selected").val(), 
+                          $("#sensor-set").val(), $("#samplerate-result"));
+          });
        });       
       }).fail(function () {
         $(document).ready(function () {
                 <li><a href="index.html"><span>Home</span></a></li>
                 <li><a href="control.html"><span>Experiment control</span></a></li>
                 <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="values.html"><span>Experiment data (live)</span></a></li>
                 <li><a href="data.html"><span>Experiment data</span></a></li>
                 <li><a href="pintest.html"><span>Pin debugging</span></a></li>
                 <li class="last"><a href="help.html"><span>Help</span></a></li>
                 &nbsp;
               </p>
               <p class="centre">
-                <input type="submit" name="start_strain" value="Strain test">
-                <input type="submit" name="start_explode" value="Explode test">                
+                <input type="button" name="start_explode" value="Explode test">
+                <input type="button" name="start_strain" value="Strain test">                
               </p>
             </form>
           </div>
                 </tr>
               </table>
               <p class="left" id="pressure-result">
-                
+                &nbsp;
               </p>
               <p class="right">
+                <input type="button" name="clear" value="Clear input">
+                <input type="button" name="zero" value="Zero the pressure">
+                <input type="button" name="step-it" value="Step the pressure">
                 <input type="submit" value="Set pressure">
               </p>
             </form>
-          </div>
-          
-          <div id="stats-widget" class="widget">
-            <form id="stats" action="#" class="nice clear">
+            
+            <form id="samplerate-controls" action="#" class="nice clear">
               <table>
                 <tr>
-                  <td><label for="stats-mainspressure">Mains pressure (kPa)</label></td>
-                  <td><label for="stats-canpressure">Can pressure (kPa)</label></td>
+                  <td><label for="sensor-select">Select a sensor</label></td>
+                  <td><label for="sensor-set">Set sampling rate (s)</label></td>
                 </tr>
                 <tr>
-                  <td><input id="stats-mainspressure" type="text" readonly></td>
-                  <td><input id="stats-canpressure" type="text" readonly></td>
-                </tr>
-                <tr class="stats-strain">
-                  <td><label for="stats-strain1">Central hoop strain</label></td>
-                  <td><label for="stats-strain2">Central longitudinal strain</label></td>
-                </tr>
-                <tr class="stats-strain">
-                  <td><input id="stats-strain1" type="text" readonly></td>
-                  <td><input id="stats-strain2" type="text" readonly></td>
-                </tr>
-                <tr class="stats-strain">
-                  <td><label for="stats-strain3">End hoop strain</label></td>
-                  <td><label for="stats-strain4">End longitudinal strain</label></td>
-                </tr>
-                <tr class="stats-strain">
-                  <td><input id="stats-strain3" type="text" readonly></td>
-                  <td><input id="stats-strain4" type="text" readonly></td>
-                </tr>
-                <tr class="stats-strain">
-                  <td><label for="stats-dilatometer">Dilatometer reading</label></td>
-                  <td><label for="stats-dilatometer">Camera feed</label></td>
-                </tr>
-                <tr class="stats-strain">
-                  <td><input id="stats-dilatometer" type="text" readonly></td>
-                  <td><a href="#">Link</a></td>
+                  <td><select id="sensor-select" style="width:100%;"></select></td>
+                  <td><input id="sensor-set" type="text"></td>
                 </tr>
               </table>
+              <p class="left" id="samplerate-result">
+                &nbsp;
+              </p>
+              <p class="right">
+                <input type="submit" value="Set sampling rate">
+              </p>
             </form>
           </div>
-          
         </div>
         <!-- End main content -->
       </div>
index c4c2807..b17b48f 100644 (file)
         $(document).ready(function() {
         });
       })
-
-      //function to load appropriate graph image depending on drop-down menu
-      function graphLoad(graphid, folder, newgraph) {
-        document.getElementById(graphid).src = folder + "/" + newgraph + ".png";
-      }
-      //function to load appropriate graph image depending on drop-down menu
-      function graphLoad2(graphid, newgraph) {
-        var exp = document.getElementById("expselect").value;
-        document.getElementById(graphid).src = exp + "/" + newgraph + ".png";
-      }
-      //function to create appropriate experiment links depending on drop-down menu
-      function expLoad(expclass, folder, newlink) {
-        var elems = document.getElementsByClassName(expclass);
-        for (i = 0; i < elems.length; i++) {
-          var elem = elems[i];
-          elem.href = folder + "/" + newlink;
-        }
-      }
-      //function to update experiment links links depending on drop-down menu
-      function expLoadTotal(folder) {
-        expLoad('s1', folder, 'strain1');
-        expLoad('s2', folder, 'strain2');
-        expLoad('s3', folder, 'strain3');
-        expLoad('s4', folder, 'strain4');
-        expLoad('p1', folder, 'pressure1');
-        expLoad('p2', folder, 'pressure2');
-        expLoad('d', folder, 'all.zip');
+      //function to load new experiment depending on drop-down menu
+      function expLoad(expName) {
+               window.open("api/control?action=load&name=" + expName);
       }
     </script>
   </head>
                 <li><a href="index.html"><span>Home</span></a></li>
                 <li><a href="control.html"><span>Experiment control</span></a></li>
                 <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="values.html"><span>Experiment data (live)</span></a></li>
                 <li><a href="data.html"><span>Experiment data</span></a></li>
                 <li><a href="pintest.html"><span>Pin debugging</span></a></li>
                 <li class="last"><a href="help.html"><span>Help</span></a></li>
               </ul>
             </div>
+          </div>
+                               <div class="widget justify">
+            <div class="title">Help</div>
+                       <p>To view the desired data as a text file, click on the appropriate link. Dilatometer data and camera data can be viewed on their separate pages. To graph the
+                       data, use the <a href="graph.html">graphs</a> page.</p>
+                       <p>To view data from previous experiments, select the experiment (sorted by date) by using the drop-down menu.</p>
           </div>
         </div>
         <!-- End sidebar -->
         <div id="main">
           <div class="widget">
             <div class="title">Experiment Data</div>
-            <b>Current Experiment</b>
+            <b>Select An Experiment</b>
+              <p><select id="expselect" onChange="expLoad(this.value)">
+                <option value="current">Current (12-12-13)</option>
+                <option value="exp2">Experiment 1 (12-10-13)</option>
+                <option value="exp3">Experiment 2 (15-10-13)</option>
+                <option value="exp4">Experiment 3 (01-11-13)</option>
+                <option value="exp5">Experiment 4 (27-11-13)</option>
+                               <option value="exp6">Experiment 5 (28-11-13)</option>
+              </select></p>
             <table>
-              <tr>
-                <!--I have no idea how we'll end up storing all the experiment data on the Beaglebone so these currently just link to a bunch of dummy files-->
-                <td>View Sensor Data</td>
-                <td></td>
-                <td><a href="current/strain1">Strain 1</a></td>
-                <td><a href="current/strain2">Strain 2</a></td>
-                <td><a href="current/strain3">Strain 3</a></td>
-                <td><a href="current/strain4">Strain 4</a></td>
-                <td><a href="current/pressure1">Pressure 1</a></td>
-                <td><a href="current/pressure2">Pressure 2</a></td>
-              </tr>
+                         <tr>
+                           <td>Experiment Date</td>
+                           <td><em>Date Lookup</em></td>
+                         </tr>
               <tr>
                 <!--uses the download attribute to force downloads, only compatible with some browsers. Other users will just have to right-click and Save File As-->
-                <td>Save Sensor Data</td>
-                <td><a href="current/all.zip" download><input type="button" value="Download All"></a></td>
-                <td><a href="current/strain1" download><input type="button" value="Strain 1"></a></td>
-                <td><a href="current/strain2" download><input type="button" value="Strain 2"></a></td>
-                <td><a href="current/strain3" download><input type="button" value="Strain 3"></a></td>
-                <td><a href="current/strain4" download><input type="button" value="Strain 4"></a></td>
-                <td><a href="current/pressure1" download><input type="button" value="Pressure 1"></a></td>
-                <td><a href="current/pressure1" download><input type="button" value="Pressure 1"></a></td>
-              </tr>
-              <tr>
-                <!--not sure about how exactly we'll be linking to dilatometer or camera data yet-->
-                <td>Dilatometer</td>
-                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
-                <td><a href="nowhere">View Data</a></td>
-              </tr>
-              <tr>
-                <td>Camera Data</td>
-                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
-                <td><a href="nowhere">View Data</a></td>
-              </tr>
-              <tr>
-
-                <td>Graphs</td>
-                <td>
-                  <form>
-                    <select onChange="graphLoad('g1', 'current', this.value)">
-                      <!--link this to graphs functionality, currently just loads a placeholder image-->
-                      <!--graphs could just plot the data over entire time range of the experiment-->
-                      <option value="nograph">Choose to view</option>
-                      <option value="graph1">Strain 1</option>
-                      <option value="graph2">Strain 2</option>
-                      <option value="graph3">Strain 3</option>
-                      <option value="graph4">Strain 4</option>
-                      <option value="graph5">Pressure 1</option>
-                      <option value="graph6">Pressure 2</option>
-                    </select>
-                  </form>
-
-                </td>
-              </tr>
-            </table>
-            <img src="current/nograph.png" id="g1">
-            <p><b>Previous Experiments</b></p>
-            <table>
-              <tr>
-                <td>Select Experiment</td>
-                <td>
-                  <!--Upon selecting experiment, modify all the download links-->
-                  <!--TODO: prevent downloads of data if no experiment is selected-->
-                  <select id="expselect" onChange="expLoadTotal(this.value)">
-                    <option value="current">Current</option>
-                    <option value="exp1">Experiment 1</option>
-                    <option value="exp2">Experiment 2</option>
-                    <option value="exp3">Experiment 3</option>
-                    <option value="exp4">Experiment 4</option>
-                    <option value="exp5">Experiment 5</option>
-                  </select>
-                </td>
-              </tr>
-              <tr>
-                <td>Experiment Date</td>
-                <!--Lookup the experiment date here for identification purposes-->
-                <td><b>DATE LOOKUP</b></td>
-              </tr>
-              <tr>
-                <td>View Sensor Data</td>
-                <td></td>
-                <td><a href="current/strain1" class="s1">Strain 1</a></td>
-                <td><a href="current/strain2" class="s2">Strain 2</a></td>
-                <td><a href="current/strain3" class="s3">Strain 3</a></td>
-                <td><a href="current/strain4" class="s4">Strain 4</a></td>
-                <td><a href="current/pressure1" class="p1">Pressure 1</a></td>
-                <td><a href="current/pressure2" class="p2">Pressure 2</a></td>
-              </tr>
-              <tr>
-                <td>Save Sensor Data</td>
-                <td><a href="current/all.zip" download class="d"><input type="button" value="Download All"></a></td>
-                <td><a href="current/strain1" download class="s1"><input type="button" value="Strain 1"></a></td>
-                <td><a href="current/strain2" download class="s2"><input type="button" value="Strain 2"></a></td>
-                <td><a href="current/strain3" download class="s3"><input type="button" value="Strain 3"></a></td>
-                <td><a href="current/strain4" download class="s4"><input type="button" value="Strain 4"></a></td>
-                <td><a href="current/pressure1" download class="p1"><input type="button" value="Pressure 1"></a></td>
-                <td><a href="current/pressure2" download class="p2"><input type="button" value="Pressure 1"></a></td>
+                <td><a class="datalink" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors#strain-gauges" target="_blank">Strain Data</a></td>
+                <td><a href="current/strain.zip" download><input type="button" value="Download All"></a></td>
+                <td><input type="button" value="Strain Gauge 1" onclick="window.open('api/sensors?start_time=0&format=tsv&id=0')"></td>
+                <td><input type="button" value="Strain Gauge 2" onclick="window.open('api/sensors?start_time=0&format=tsv&id=1')"></td>
+                <td><input type="button" value="Strain Gauge 3" onclick="window.open('api/sensors?start_time=0&format=tsv&id=2')"></td>
+                <td><input type="button" value="Strain Gauge 4" onclick="window.open('api/sensors?start_time=0&format=tsv&id=3')"></td>
+                         </tr>
+                         <tr>
+                           <td><a class="datalink" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors#pressure-sensors" target="_blank">Pressure Data</a></td>
+                <td><a href="current/pressure.zip" download><input type="button" value="Download All"></a></td>
+                <td><input type="button" value="Pressure Sensor 1" onclick="window.open('api/sensors?start_time=0&format=tsv&id=4')"></td>
+                <td><input type="button" value="Pressure Sensor 2" onclick="window.open('api/sensors?start_time=0&format=tsv&id=5')"></td>
+                               <td><input type="button" value="Pressure Sensor 3" onclick="window.open('api/sensors?start_time=0&format=tsv&id=6')"></td>
               </tr>
               <tr>
-                <!--not sure about how exactly we'll be linking to dilatometer or camera data yet-->
-                <td>Dilatometer</td>
-                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
-                <td><a href="nowhere">View Data</a></td>
+                <!--this assumes dilatometer and camera data will be kept on separate pages in the GUI-->
+                <td><a class="datalink" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors#dilatometer" target="_blank">Dilatometer</a></td>
+                <td><a href="dilatometer.zip" download=><input type="button" value="Download Data"></a></td>
+                <td><a href="dilatometer.html">View Data</a></td>
               </tr>
               <tr>
-                <td>Camera Data</td>
-                <td><a href="nowhere" download="nowhere"><input type="button" value="Download Data"></a></td>
-                <td><a href="nowhere">View Data</a></td>
+                <td><a class="datalink" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors#camera" target="_blank">Camera Data</a></td>
+                <td><a href="image.jpeg" download><input type="button" value="Download Data"></a></td>
+                <td><a href="image.html">View Data</a></td>
               </tr>
               <tr>
                 <td>Graphs</td>
-                <td>
-                  <form>
-                    <select onChange="graphLoad2('g2', this.value)">
-                      <!--link this to graphs functionality, currently just loads a placeholder image-->
-                      <!--graphs could just plot the data over entire time range of the experiment-->
-                      <option value="nograph">Choose to view</option>
-                      <option value="graph1">Strain 1</option>
-                      <option value="graph2">Strain 2</option>
-                      <option value="graph3">Strain 3</option>
-                      <option value="graph4">Strain 4</option>
-                      <option value="graph5">Pressure 1</option>
-                      <option value="graph6">Pressure 2</option>
-                    </select>
-                  </form>
-                </td>
+                <td><a href="graph.html">Graphs Page</a></td>
               </tr>
             </table>
-            <img src="current/nograph.png" id="g2">
           </div>
         </div>
         <!-- End main content -->
index b7d87f1..09e792a 100644 (file)
@@ -8,9 +8,11 @@
     <![endif]-->
     <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
     <script type="text/javascript" src="static/jquery.flot.min.js"></script>
-
+    <script type="text/javascript" src="static/html2canvas.js"></script>
+    
     <script type="text/javascript" src="static/mctx.gui.js"></script>
     <script type="text/javascript" src="static/mctx.graph.js"></script>
+    
     <link rel="stylesheet" type="text/css" href="static/style.css">
     <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
     <script type="text/javascript">
@@ -72,6 +74,7 @@
                 <li><a href="index.html"><span>Home</span></a></li>
                 <li><a href="control.html"><span>Experiment control</span></a></li>
                 <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="values.html"><span>Experiment data (live)</span></a></li>
                 <li><a href="data.html"><span>Experiment data</span></a></li>
                 <li><a href="pintest.html"><span>Pin debugging</span></a></li>
                 <li class="last"><a href="help.html"><span>Help</span></a></li>
         <!-- End sidebar -->
 
         <div id="main">
-          <div class="widget">
+          <div class="widget" id="graph-widget">
             <div class="title">Graph</div>
             <!-- graph placeholder -->
-            <div id="graph" class="plot"></div>
+            <div id="graph-container">
+              <div id="graph" class="plot"></div>
+              <div id="graph-legend"></div>
+            </div>
           </div>
           <div class="widget" id="graph-controls">
             <!--<div class="title">Visualise</div>-->
             <!--b>Right Y-Axis</b>
             <form id="y2axis" onChange=$("#graph").setGraph()> <input type="radio" name="y2axis" value="none" id="none" checked="yes">none</form>-->
 
-            <div> 
-              <form id="time_range" class="change">
-                <p> 
-                  Time of Last Update <input type="text" value="" id="current_time" disabled>
-                  Start Time <input type="text" value="" id="start_time"> 
-                  End Time <input type="text" value="" id="end_time">
-                </p>
-              </form>
-            </div>
+
+            <form id="time_range" class="change">
+              <p> 
+                Time of Last Update <input type="text" value="" id="current_time" disabled>
+                Start Time <input type="text" value="" id="start_time"> 
+                End Time <input type="text" value="" id="end_time">
+              </p>
+            </form>
+
             <input type="button" value="Run" id="graph-run" onClick="$('#graph-run').runButton()">
             <input type="button" value="Open New Graph" disabled>
             <input type="button" value="Save Graph Image" id="saveimage">
             <input type="button" value="Dump Raw Data" disabled>
             <script type="text/javascript">
               $("#saveimage").click(function() {
-                $("canvas").each(function() {
-                  var image = new Image();
-
-                  window.open(this.toDataURL("image/png"));
-                  $("#graph-controls").append(image);
-                });
+                  html2canvas($("#graph-widget")[0], {
+                    onrendered: function(canvas) {
+                      var context = canvas.getContext('2d');
+                      context.webkitImageSmoothingEnabled = false;//webkit
+                      context.mozImageSmoothingEnabled = false;//firefox
+                      context.imageSmoothingEnabled = false;
+                      
+                      window.open(canvas.toDataURL("image/png"));
+                      
+                      /*
+                      var canvasOut = document.createElement("canvas");
+                      var ratio = canvas.height / canvas.width;
+                      var width = 1600;
+                      var height = width * ratio;
+                      canvasOut.setAttribute('width', width);
+                      canvasOut.setAttribute('height', height);
+                      
+                      context = canvasOut.getContext('2d');
+                      context.webkitImageSmoothingEnabled = false;//webkit
+                      context.mozImageSmoothingEnabled = false;//firefox
+                      context.imageSmoothingEnabled = false;
+                      context.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, width, height);
+                      
+                      window.open(canvasOut.toDataURL("image/png"))*/
+                    }
+                  });
               });
             </script>
             
index 5760c4e..4daf5a8 100644 (file)
@@ -59,6 +59,7 @@
                 <li><a href="index.html"><span>Home</span></a></li>
                 <li><a href="control.html"><span>Experiment control</span></a></li>
                 <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="values.html"><span>Experiment data (live)</span></a></li>
                 <li><a href="data.html"><span>Experiment data</span></a></li>
                 <li><a href="pintest.html"><span>Pin debugging</span></a></li>
                 <li class="last"><a href="help.html"><span>Help</span></a></li>
index b91db20..f265780 100644 (file)
@@ -70,6 +70,7 @@
                 <li><a href="index.html"><span>Home</span></a></li>
                 <li><a href="control.html"><span>Experiment control</span></a></li>
                 <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="values.html"><span>Experiment data (live)</span></a></li>
                 <li><a href="data.html"><span>Experiment data</span></a></li>
                 <li><a href="pintest.html"><span>Pin debugging</span></a></li>
                 <li class="last"><a href="help.html"><span>Help</span></a></li>
index c19dd73..774034a 100644 (file)
@@ -43,7 +43,7 @@
     <div id="content-wrap">
       <div id="login-container">
        <div class="widget">
-           <form id="login" action="#">
+           <form id="login" action="#" method="post">
              <p>
                <label>
                  Username<br>
index 9372ee8..92abf93 100644 (file)
                 <li><a href="index.html"><span>Home</span></a></li>
                 <li><a href="control.html"><span>Experiment control</span></a></li>
                 <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="values.html"><span>Experiment data (live)</span></a></li>
                 <li><a href="data.html"><span>Experiment data</span></a></li>
                 <li><a href="pintest.html"><span>Pin debugging</span></a></li>
                 <li class="last"><a href="help.html"><span>Help</span></a></li>
diff --git a/testing/MCTXWeb/public_html/static/html2canvas.js b/testing/MCTXWeb/public_html/static/html2canvas.js
new file mode 100644 (file)
index 0000000..3a44bc3
--- /dev/null
@@ -0,0 +1,2868 @@
+/*
+  html2canvas 0.4.1 <http://html2canvas.hertzen.com>
+  Copyright (c) 2013 Niklas von Hertzen
+
+  Released under MIT License
+*/
+
+(function(window, document, undefined){
+
+"use strict";
+
+var _html2canvas = {},
+previousElement,
+computedCSS,
+html2canvas;
+
+_html2canvas.Util = {};
+
+_html2canvas.Util.log = function(a) {
+  if (_html2canvas.logging && window.console && window.console.log) {
+    window.console.log(a);
+  }
+};
+
+_html2canvas.Util.trimText = (function(isNative){
+  return function(input) {
+    return isNative ? isNative.apply(input) : ((input || '') + '').replace( /^\s+|\s+$/g , '' );
+  };
+})(String.prototype.trim);
+
+_html2canvas.Util.asFloat = function(v) {
+  return parseFloat(v);
+};
+
+(function() {
+  // TODO: support all possible length values
+  var TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
+  var TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
+  _html2canvas.Util.parseTextShadows = function (value) {
+    if (!value || value === 'none') {
+      return [];
+    }
+
+    // find multiple shadow declarations
+    var shadows = value.match(TEXT_SHADOW_PROPERTY),
+      results = [];
+    for (var i = 0; shadows && (i < shadows.length); i++) {
+      var s = shadows[i].match(TEXT_SHADOW_VALUES);
+      results.push({
+        color: s[0],
+        offsetX: s[1] ? s[1].replace('px', '') : 0,
+        offsetY: s[2] ? s[2].replace('px', '') : 0,
+        blur: s[3] ? s[3].replace('px', '') : 0
+      });
+    }
+    return results;
+  };
+})();
+
+
+_html2canvas.Util.parseBackgroundImage = function (value) {
+    var whitespace = ' \r\n\t',
+        method, definition, prefix, prefix_i, block, results = [],
+        c, mode = 0, numParen = 0, quote, args;
+
+    var appendResult = function(){
+        if(method) {
+            if(definition.substr( 0, 1 ) === '"') {
+                definition = definition.substr( 1, definition.length - 2 );
+            }
+            if(definition) {
+                args.push(definition);
+            }
+            if(method.substr( 0, 1 ) === '-' &&
+                    (prefix_i = method.indexOf( '-', 1 ) + 1) > 0) {
+                prefix = method.substr( 0, prefix_i);
+                method = method.substr( prefix_i );
+            }
+            results.push({
+                prefix: prefix,
+                method: method.toLowerCase(),
+                value: block,
+                args: args
+            });
+        }
+        args = []; //for some odd reason, setting .length = 0 didn't work in safari
+        method =
+            prefix =
+            definition =
+            block = '';
+    };
+
+    appendResult();
+    for(var i = 0, ii = value.length; i<ii; i++) {
+        c = value[i];
+        if(mode === 0 && whitespace.indexOf( c ) > -1){
+            continue;
+        }
+        switch(c) {
+            case '"':
+                if(!quote) {
+                    quote = c;
+                }
+                else if(quote === c) {
+                    quote = null;
+                }
+                break;
+
+            case '(':
+                if(quote) { break; }
+                else if(mode === 0) {
+                    mode = 1;
+                    block += c;
+                    continue;
+                } else {
+                    numParen++;
+                }
+                break;
+
+            case ')':
+                if(quote) { break; }
+                else if(mode === 1) {
+                    if(numParen === 0) {
+                        mode = 0;
+                        block += c;
+                        appendResult();
+                        continue;
+                    } else {
+                        numParen--;
+                    }
+                }
+                break;
+
+            case ',':
+                if(quote) { break; }
+                else if(mode === 0) {
+                    appendResult();
+                    continue;
+                }
+                else if (mode === 1) {
+                    if(numParen === 0 && !method.match(/^url$/i)) {
+                        args.push(definition);
+                        definition = '';
+                        block += c;
+                        continue;
+                    }
+                }
+                break;
+        }
+
+        block += c;
+        if(mode === 0) { method += c; }
+        else { definition += c; }
+    }
+    appendResult();
+
+    return results;
+};
+
+_html2canvas.Util.Bounds = function (element) {
+  var clientRect, bounds = {};
+
+  if (element.getBoundingClientRect){
+    clientRect = element.getBoundingClientRect();
+
+    // TODO add scroll position to bounds, so no scrolling of window necessary
+    bounds.top = clientRect.top;
+    bounds.bottom = clientRect.bottom || (clientRect.top + clientRect.height);
+    bounds.left = clientRect.left;
+
+    bounds.width = element.offsetWidth;
+    bounds.height = element.offsetHeight;
+  }
+
+  return bounds;
+};
+
+// TODO ideally, we'd want everything to go through this function instead of Util.Bounds,
+// but would require further work to calculate the correct positions for elements with offsetParents
+_html2canvas.Util.OffsetBounds = function (element) {
+  var parent = element.offsetParent ? _html2canvas.Util.OffsetBounds(element.offsetParent) : {top: 0, left: 0};
+
+  return {
+    top: element.offsetTop + parent.top,
+    bottom: element.offsetTop + element.offsetHeight + parent.top,
+    left: element.offsetLeft + parent.left,
+    width: element.offsetWidth,
+    height: element.offsetHeight
+  };
+};
+
+function toPX(element, attribute, value ) {
+    var rsLeft = element.runtimeStyle && element.runtimeStyle[attribute],
+        left,
+        style = element.style;
+
+    // Check if we are not dealing with pixels, (Opera has issues with this)
+    // Ported from jQuery css.js
+    // From the awesome hack by Dean Edwards
+    // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+    // If we're not dealing with a regular pixel number
+    // but a number that has a weird ending, we need to convert it to pixels
+
+    if ( !/^-?[0-9]+\.?[0-9]*(?:px)?$/i.test( value ) && /^-?\d/.test(value) ) {
+        // Remember the original values
+        left = style.left;
+
+        // Put in the new values to get a computed value out
+        if (rsLeft) {
+            element.runtimeStyle.left = element.currentStyle.left;
+        }
+        style.left = attribute === "fontSize" ? "1em" : (value || 0);
+        value = style.pixelLeft + "px";
+
+        // Revert the changed values
+        style.left = left;
+        if (rsLeft) {
+            element.runtimeStyle.left = rsLeft;
+        }
+    }
+
+    if (!/^(thin|medium|thick)$/i.test(value)) {
+        return Math.round(parseFloat(value)) + "px";
+    }
+
+    return value;
+}
+
+function asInt(val) {
+    return parseInt(val, 10);
+}
+
+function parseBackgroundSizePosition(value, element, attribute, index) {
+    value = (value || '').split(',');
+    value = value[index || 0] || value[0] || 'auto';
+    value = _html2canvas.Util.trimText(value).split(' ');
+
+    if(attribute === 'backgroundSize' && (!value[0] || value[0].match(/cover|contain|auto/))) {
+        //these values will be handled in the parent function
+    } else {
+        value[0] = (value[0].indexOf( "%" ) === -1) ? toPX(element, attribute + "X", value[0]) : value[0];
+        if(value[1] === undefined) {
+            if(attribute === 'backgroundSize') {
+                value[1] = 'auto';
+                return value;
+            } else {
+                // IE 9 doesn't return double digit always
+                value[1] = value[0];
+            }
+        }
+        value[1] = (value[1].indexOf("%") === -1) ? toPX(element, attribute + "Y", value[1]) : value[1];
+    }
+    return value;
+}
+
+_html2canvas.Util.getCSS = function (element, attribute, index) {
+    if (previousElement !== element) {
+      computedCSS = document.defaultView.getComputedStyle(element, null);
+    }
+
+    var value = computedCSS[attribute];
+
+    if (/^background(Size|Position)$/.test(attribute)) {
+        return parseBackgroundSizePosition(value, element, attribute, index);
+    } else if (/border(Top|Bottom)(Left|Right)Radius/.test(attribute)) {
+      var arr = value.split(" ");
+      if (arr.length <= 1) {
+          arr[1] = arr[0];
+      }
+      return arr.map(asInt);
+    }
+
+  return value;
+};
+
+_html2canvas.Util.resizeBounds = function( current_width, current_height, target_width, target_height, stretch_mode ){
+  var target_ratio = target_width / target_height,
+    current_ratio = current_width / current_height,
+    output_width, output_height;
+
+  if(!stretch_mode || stretch_mode === 'auto') {
+    output_width = target_width;
+    output_height = target_height;
+  } else if(target_ratio < current_ratio ^ stretch_mode === 'contain') {
+    output_height = target_height;
+    output_width = target_height * current_ratio;
+  } else {
+    output_width = target_width;
+    output_height = target_width / current_ratio;
+  }
+
+  return {
+    width: output_width,
+    height: output_height
+  };
+};
+
+function backgroundBoundsFactory( prop, el, bounds, image, imageIndex, backgroundSize ) {
+    var bgposition =  _html2canvas.Util.getCSS( el, prop, imageIndex ) ,
+    topPos,
+    left,
+    percentage,
+    val;
+
+    if (bgposition.length === 1){
+      val = bgposition[0];
+
+      bgposition = [];
+
+      bgposition[0] = val;
+      bgposition[1] = val;
+    }
+
+    if (bgposition[0].toString().indexOf("%") !== -1){
+      percentage = (parseFloat(bgposition[0])/100);
+      left = bounds.width * percentage;
+      if(prop !== 'backgroundSize') {
+        left -= (backgroundSize || image).width*percentage;
+      }
+    } else {
+      if(prop === 'backgroundSize') {
+        if(bgposition[0] === 'auto') {
+          left = image.width;
+        } else {
+          if (/contain|cover/.test(bgposition[0])) {
+            var resized = _html2canvas.Util.resizeBounds(image.width, image.height, bounds.width, bounds.height, bgposition[0]);
+            left = resized.width;
+            topPos = resized.height;
+          } else {
+            left = parseInt(bgposition[0], 10);
+          }
+        }
+      } else {
+        left = parseInt( bgposition[0], 10);
+      }
+    }
+
+
+    if(bgposition[1] === 'auto') {
+      topPos = left / image.width * image.height;
+    } else if (bgposition[1].toString().indexOf("%") !== -1){
+      percentage = (parseFloat(bgposition[1])/100);
+      topPos =  bounds.height * percentage;
+      if(prop !== 'backgroundSize') {
+        topPos -= (backgroundSize || image).height * percentage;
+      }
+
+    } else {
+      topPos = parseInt(bgposition[1],10);
+    }
+
+    return [left, topPos];
+}
+
+_html2canvas.Util.BackgroundPosition = function( el, bounds, image, imageIndex, backgroundSize ) {
+    var result = backgroundBoundsFactory( 'backgroundPosition', el, bounds, image, imageIndex, backgroundSize );
+    return { left: result[0], top: result[1] };
+};
+
+_html2canvas.Util.BackgroundSize = function( el, bounds, image, imageIndex ) {
+    var result = backgroundBoundsFactory( 'backgroundSize', el, bounds, image, imageIndex );
+    return { width: result[0], height: result[1] };
+};
+
+_html2canvas.Util.Extend = function (options, defaults) {
+  for (var key in options) {
+    if (options.hasOwnProperty(key)) {
+      defaults[key] = options[key];
+    }
+  }
+  return defaults;
+};
+
+
+/*
+ * Derived from jQuery.contents()
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ */
+_html2canvas.Util.Children = function( elem ) {
+  var children;
+  try {
+    children = (elem.nodeName && elem.nodeName.toUpperCase() === "IFRAME") ? elem.contentDocument || elem.contentWindow.document : (function(array) {
+      var ret = [];
+      if (array !== null) {
+        (function(first, second ) {
+          var i = first.length,
+          j = 0;
+
+          if (typeof second.length === "number") {
+            for (var l = second.length; j < l; j++) {
+              first[i++] = second[j];
+            }
+          } else {
+            while (second[j] !== undefined) {
+              first[i++] = second[j++];
+            }
+          }
+
+          first.length = i;
+
+          return first;
+        })(ret, array);
+      }
+      return ret;
+    })(elem.childNodes);
+
+  } catch (ex) {
+    _html2canvas.Util.log("html2canvas.Util.Children failed with exception: " + ex.message);
+    children = [];
+  }
+  return children;
+};
+
+_html2canvas.Util.isTransparent = function(backgroundColor) {
+  return (backgroundColor === "transparent" || backgroundColor === "rgba(0, 0, 0, 0)");
+};
+_html2canvas.Util.Font = (function () {
+
+  var fontData = {};
+
+  return function(font, fontSize, doc) {
+    if (fontData[font + "-" + fontSize] !== undefined) {
+      return fontData[font + "-" + fontSize];
+    }
+
+    var container = doc.createElement('div'),
+    img = doc.createElement('img'),
+    span = doc.createElement('span'),
+    sampleText = 'Hidden Text',
+    baseline,
+    middle,
+    metricsObj;
+
+    container.style.visibility = "hidden";
+    container.style.fontFamily = font;
+    container.style.fontSize = fontSize;
+    container.style.margin = 0;
+    container.style.padding = 0;
+
+    doc.body.appendChild(container);
+
+    // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever (handtinywhite.gif)
+    img.src = "data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=";
+    img.width = 1;
+    img.height = 1;
+
+    img.style.margin = 0;
+    img.style.padding = 0;
+    img.style.verticalAlign = "baseline";
+
+    span.style.fontFamily = font;
+    span.style.fontSize = fontSize;
+    span.style.margin = 0;
+    span.style.padding = 0;
+
+    span.appendChild(doc.createTextNode(sampleText));
+    container.appendChild(span);
+    container.appendChild(img);
+    baseline = (img.offsetTop - span.offsetTop) + 1;
+
+    container.removeChild(span);
+    container.appendChild(doc.createTextNode(sampleText));
+
+    container.style.lineHeight = "normal";
+    img.style.verticalAlign = "super";
+
+    middle = (img.offsetTop-container.offsetTop) + 1;
+    metricsObj = {
+      baseline: baseline,
+      lineWidth: 1,
+      middle: middle
+    };
+
+    fontData[font + "-" + fontSize] = metricsObj;
+
+    doc.body.removeChild(container);
+
+    return metricsObj;
+  };
+})();
+
+(function(){
+  var Util = _html2canvas.Util,
+    Generate = {};
+
+  _html2canvas.Generate = Generate;
+
+  var reGradients = [
+  /^(-webkit-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
+  /^(-o-linear-gradient)\(([a-z\s]+)([\w\d\.\s,%\(\)]+)\)$/,
+  /^(-webkit-gradient)\((linear|radial),\s((?:\d{1,3}%?)\s(?:\d{1,3}%?),\s(?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)\-]+)\)$/,
+  /^(-moz-linear-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?))([\w\d\.\s,%\(\)]+)\)$/,
+  /^(-webkit-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/,
+  /^(-moz-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s?([a-z\-]*)([\w\d\.\s,%\(\)]+)\)$/,
+  /^(-o-radial-gradient)\(((?:\d{1,3}%?)\s(?:\d{1,3}%?)),\s(\w+)\s([a-z\-]+)([\w\d\.\s,%\(\)]+)\)$/
+  ];
+
+  /*
+ * TODO: Add IE10 vendor prefix (-ms) support
+ * TODO: Add W3C gradient (linear-gradient) support
+ * TODO: Add old Webkit -webkit-gradient(radial, ...) support
+ * TODO: Maybe some RegExp optimizations are possible ;o)
+ */
+  Generate.parseGradient = function(css, bounds) {
+    var gradient, i, len = reGradients.length, m1, stop, m2, m2Len, step, m3, tl,tr,br,bl;
+
+    for(i = 0; i < len; i+=1){
+      m1 = css.match(reGradients[i]);
+      if(m1) {
+        break;
+      }
+    }
+
+    if(m1) {
+      switch(m1[1]) {
+        case '-webkit-linear-gradient':
+        case '-o-linear-gradient':
+
+          gradient = {
+            type: 'linear',
+            x0: null,
+            y0: null,
+            x1: null,
+            y1: null,
+            colorStops: []
+          };
+
+          // get coordinates
+          m2 = m1[2].match(/\w+/g);
+          if(m2){
+            m2Len = m2.length;
+            for(i = 0; i < m2Len; i+=1){
+              switch(m2[i]) {
+                case 'top':
+                  gradient.y0 = 0;
+                  gradient.y1 = bounds.height;
+                  break;
+
+                case 'right':
+                  gradient.x0 = bounds.width;
+                  gradient.x1 = 0;
+                  break;
+
+                case 'bottom':
+                  gradient.y0 = bounds.height;
+                  gradient.y1 = 0;
+                  break;
+
+                case 'left':
+                  gradient.x0 = 0;
+                  gradient.x1 = bounds.width;
+                  break;
+              }
+            }
+          }
+          if(gradient.x0 === null && gradient.x1 === null){ // center
+            gradient.x0 = gradient.x1 = bounds.width / 2;
+          }
+          if(gradient.y0 === null && gradient.y1 === null){ // center
+            gradient.y0 = gradient.y1 = bounds.height / 2;
+          }
+
+          // get colors and stops
+          m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
+          if(m2){
+            m2Len = m2.length;
+            step = 1 / Math.max(m2Len - 1, 1);
+            for(i = 0; i < m2Len; i+=1){
+              m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
+              if(m3[2]){
+                stop = parseFloat(m3[2]);
+                if(m3[3] === '%'){
+                  stop /= 100;
+                } else { // px - stupid opera
+                  stop /= bounds.width;
+                }
+              } else {
+                stop = i * step;
+              }
+              gradient.colorStops.push({
+                color: m3[1],
+                stop: stop
+              });
+            }
+          }
+          break;
+
+        case '-webkit-gradient':
+
+          gradient = {
+            type: m1[2] === 'radial' ? 'circle' : m1[2], // TODO: Add radial gradient support for older mozilla definitions
+            x0: 0,
+            y0: 0,
+            x1: 0,
+            y1: 0,
+            colorStops: []
+          };
+
+          // get coordinates
+          m2 = m1[3].match(/(\d{1,3})%?\s(\d{1,3})%?,\s(\d{1,3})%?\s(\d{1,3})%?/);
+          if(m2){
+            gradient.x0 = (m2[1] * bounds.width) / 100;
+            gradient.y0 = (m2[2] * bounds.height) / 100;
+            gradient.x1 = (m2[3] * bounds.width) / 100;
+            gradient.y1 = (m2[4] * bounds.height) / 100;
+          }
+
+          // get colors and stops
+          m2 = m1[4].match(/((?:from|to|color-stop)\((?:[0-9\.]+,\s)?(?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)\))+/g);
+          if(m2){
+            m2Len = m2.length;
+            for(i = 0; i < m2Len; i+=1){
+              m3 = m2[i].match(/(from|to|color-stop)\(([0-9\.]+)?(?:,\s)?((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\)/);
+              stop = parseFloat(m3[2]);
+              if(m3[1] === 'from') {
+                stop = 0.0;
+              }
+              if(m3[1] === 'to') {
+                stop = 1.0;
+              }
+              gradient.colorStops.push({
+                color: m3[3],
+                stop: stop
+              });
+            }
+          }
+          break;
+
+        case '-moz-linear-gradient':
+
+          gradient = {
+            type: 'linear',
+            x0: 0,
+            y0: 0,
+            x1: 0,
+            y1: 0,
+            colorStops: []
+          };
+
+          // get coordinates
+          m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
+
+          // m2[1] == 0%   -> left
+          // m2[1] == 50%  -> center
+          // m2[1] == 100% -> right
+
+          // m2[2] == 0%   -> top
+          // m2[2] == 50%  -> center
+          // m2[2] == 100% -> bottom
+
+          if(m2){
+            gradient.x0 = (m2[1] * bounds.width) / 100;
+            gradient.y0 = (m2[2] * bounds.height) / 100;
+            gradient.x1 = bounds.width - gradient.x0;
+            gradient.y1 = bounds.height - gradient.y0;
+          }
+
+          // get colors and stops
+          m2 = m1[3].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}%)?)+/g);
+          if(m2){
+            m2Len = m2.length;
+            step = 1 / Math.max(m2Len - 1, 1);
+            for(i = 0; i < m2Len; i+=1){
+              m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%)?/);
+              if(m3[2]){
+                stop = parseFloat(m3[2]);
+                if(m3[3]){ // percentage
+                  stop /= 100;
+                }
+              } else {
+                stop = i * step;
+              }
+              gradient.colorStops.push({
+                color: m3[1],
+                stop: stop
+              });
+            }
+          }
+          break;
+
+        case '-webkit-radial-gradient':
+        case '-moz-radial-gradient':
+        case '-o-radial-gradient':
+
+          gradient = {
+            type: 'circle',
+            x0: 0,
+            y0: 0,
+            x1: bounds.width,
+            y1: bounds.height,
+            cx: 0,
+            cy: 0,
+            rx: 0,
+            ry: 0,
+            colorStops: []
+          };
+
+          // center
+          m2 = m1[2].match(/(\d{1,3})%?\s(\d{1,3})%?/);
+          if(m2){
+            gradient.cx = (m2[1] * bounds.width) / 100;
+            gradient.cy = (m2[2] * bounds.height) / 100;
+          }
+
+          // size
+          m2 = m1[3].match(/\w+/);
+          m3 = m1[4].match(/[a-z\-]*/);
+          if(m2 && m3){
+            switch(m3[0]){
+              case 'farthest-corner':
+              case 'cover': // is equivalent to farthest-corner
+              case '': // mozilla removes "cover" from definition :(
+                tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
+                tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
+                br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
+                bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
+                gradient.rx = gradient.ry = Math.max(tl, tr, br, bl);
+                break;
+              case 'closest-corner':
+                tl = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.cy, 2));
+                tr = Math.sqrt(Math.pow(gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
+                br = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.y1 - gradient.cy, 2));
+                bl = Math.sqrt(Math.pow(gradient.x1 - gradient.cx, 2) + Math.pow(gradient.cy, 2));
+                gradient.rx = gradient.ry = Math.min(tl, tr, br, bl);
+                break;
+              case 'farthest-side':
+                if(m2[0] === 'circle'){
+                  gradient.rx = gradient.ry = Math.max(
+                    gradient.cx,
+                    gradient.cy,
+                    gradient.x1 - gradient.cx,
+                    gradient.y1 - gradient.cy
+                    );
+                } else { // ellipse
+
+                  gradient.type = m2[0];
+
+                  gradient.rx = Math.max(
+                    gradient.cx,
+                    gradient.x1 - gradient.cx
+                    );
+                  gradient.ry = Math.max(
+                    gradient.cy,
+                    gradient.y1 - gradient.cy
+                    );
+                }
+                break;
+              case 'closest-side':
+              case 'contain': // is equivalent to closest-side
+                if(m2[0] === 'circle'){
+                  gradient.rx = gradient.ry = Math.min(
+                    gradient.cx,
+                    gradient.cy,
+                    gradient.x1 - gradient.cx,
+                    gradient.y1 - gradient.cy
+                    );
+                } else { // ellipse
+
+                  gradient.type = m2[0];
+
+                  gradient.rx = Math.min(
+                    gradient.cx,
+                    gradient.x1 - gradient.cx
+                    );
+                  gradient.ry = Math.min(
+                    gradient.cy,
+                    gradient.y1 - gradient.cy
+                    );
+                }
+                break;
+
+            // TODO: add support for "30px 40px" sizes (webkit only)
+            }
+          }
+
+          // color stops
+          m2 = m1[5].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\)(?:\s\d{1,3}(?:%|px))?)+/g);
+          if(m2){
+            m2Len = m2.length;
+            step = 1 / Math.max(m2Len - 1, 1);
+            for(i = 0; i < m2Len; i+=1){
+              m3 = m2[i].match(/((?:rgb|rgba)\(\d{1,3},\s\d{1,3},\s\d{1,3}(?:,\s[0-9\.]+)?\))\s*(\d{1,3})?(%|px)?/);
+              if(m3[2]){
+                stop = parseFloat(m3[2]);
+                if(m3[3] === '%'){
+                  stop /= 100;
+                } else { // px - stupid opera
+                  stop /= bounds.width;
+                }
+              } else {
+                stop = i * step;
+              }
+              gradient.colorStops.push({
+                color: m3[1],
+                stop: stop
+              });
+            }
+          }
+          break;
+      }
+    }
+
+    return gradient;
+  };
+
+  function addScrollStops(grad) {
+    return function(colorStop) {
+      try {
+        grad.addColorStop(colorStop.stop, colorStop.color);
+      }
+      catch(e) {
+        Util.log(['failed to add color stop: ', e, '; tried to add: ', colorStop]);
+      }
+    };
+  }
+
+  Generate.Gradient = function(src, bounds) {
+    if(bounds.width === 0 || bounds.height === 0) {
+      return;
+    }
+
+    var canvas = document.createElement('canvas'),
+    ctx = canvas.getContext('2d'),
+    gradient, grad;
+
+    canvas.width = bounds.width;
+    canvas.height = bounds.height;
+
+    // TODO: add support for multi defined background gradients
+    gradient = _html2canvas.Generate.parseGradient(src, bounds);
+
+    if(gradient) {
+      switch(gradient.type) {
+        case 'linear':
+          grad = ctx.createLinearGradient(gradient.x0, gradient.y0, gradient.x1, gradient.y1);
+          gradient.colorStops.forEach(addScrollStops(grad));
+          ctx.fillStyle = grad;
+          ctx.fillRect(0, 0, bounds.width, bounds.height);
+          break;
+
+        case 'circle':
+          grad = ctx.createRadialGradient(gradient.cx, gradient.cy, 0, gradient.cx, gradient.cy, gradient.rx);
+          gradient.colorStops.forEach(addScrollStops(grad));
+          ctx.fillStyle = grad;
+          ctx.fillRect(0, 0, bounds.width, bounds.height);
+          break;
+
+        case 'ellipse':
+          var canvasRadial = document.createElement('canvas'),
+            ctxRadial = canvasRadial.getContext('2d'),
+            ri = Math.max(gradient.rx, gradient.ry),
+            di = ri * 2;
+
+          canvasRadial.width = canvasRadial.height = di;
+
+          grad = ctxRadial.createRadialGradient(gradient.rx, gradient.ry, 0, gradient.rx, gradient.ry, ri);
+          gradient.colorStops.forEach(addScrollStops(grad));
+
+          ctxRadial.fillStyle = grad;
+          ctxRadial.fillRect(0, 0, di, di);
+
+          ctx.fillStyle = gradient.colorStops[gradient.colorStops.length - 1].color;
+          ctx.fillRect(0, 0, canvas.width, canvas.height);
+          ctx.drawImage(canvasRadial, gradient.cx - gradient.rx, gradient.cy - gradient.ry, 2 * gradient.rx, 2 * gradient.ry);
+          break;
+      }
+    }
+
+    return canvas;
+  };
+
+  Generate.ListAlpha = function(number) {
+    var tmp = "",
+    modulus;
+
+    do {
+      modulus = number % 26;
+      tmp = String.fromCharCode((modulus) + 64) + tmp;
+      number = number / 26;
+    }while((number*26) > 26);
+
+    return tmp;
+  };
+
+  Generate.ListRoman = function(number) {
+    var romanArray = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"],
+    decimal = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
+    roman = "",
+    v,
+    len = romanArray.length;
+
+    if (number <= 0 || number >= 4000) {
+      return number;
+    }
+
+    for (v=0; v < len; v+=1) {
+      while (number >= decimal[v]) {
+        number -= decimal[v];
+        roman += romanArray[v];
+      }
+    }
+
+    return roman;
+  };
+})();
+function h2cRenderContext(width, height) {
+  var storage = [];
+  return {
+    storage: storage,
+    width: width,
+    height: height,
+    clip: function() {
+      storage.push({
+        type: "function",
+        name: "clip",
+        'arguments': arguments
+      });
+    },
+    translate: function() {
+      storage.push({
+        type: "function",
+        name: "translate",
+        'arguments': arguments
+      });
+    },
+    fill: function() {
+      storage.push({
+        type: "function",
+        name: "fill",
+        'arguments': arguments
+      });
+    },
+    save: function() {
+      storage.push({
+        type: "function",
+        name: "save",
+        'arguments': arguments
+      });
+    },
+    restore: function() {
+      storage.push({
+        type: "function",
+        name: "restore",
+        'arguments': arguments
+      });
+    },
+    fillRect: function () {
+      storage.push({
+        type: "function",
+        name: "fillRect",
+        'arguments': arguments
+      });
+    },
+    createPattern: function() {
+      storage.push({
+        type: "function",
+        name: "createPattern",
+        'arguments': arguments
+      });
+    },
+    drawShape: function() {
+
+      var shape = [];
+
+      storage.push({
+        type: "function",
+        name: "drawShape",
+        'arguments': shape
+      });
+
+      return {
+        moveTo: function() {
+          shape.push({
+            name: "moveTo",
+            'arguments': arguments
+          });
+        },
+        lineTo: function() {
+          shape.push({
+            name: "lineTo",
+            'arguments': arguments
+          });
+        },
+        arcTo: function() {
+          shape.push({
+            name: "arcTo",
+            'arguments': arguments
+          });
+        },
+        bezierCurveTo: function() {
+          shape.push({
+            name: "bezierCurveTo",
+            'arguments': arguments
+          });
+        },
+        quadraticCurveTo: function() {
+          shape.push({
+            name: "quadraticCurveTo",
+            'arguments': arguments
+          });
+        }
+      };
+
+    },
+    drawImage: function () {
+      storage.push({
+        type: "function",
+        name: "drawImage",
+        'arguments': arguments
+      });
+    },
+    fillText: function () {
+      storage.push({
+        type: "function",
+        name: "fillText",
+        'arguments': arguments
+      });
+    },
+    setVariable: function (variable, value) {
+      storage.push({
+        type: "variable",
+        name: variable,
+        'arguments': value
+      });
+      return value;
+    }
+  };
+}
+_html2canvas.Parse = function (images, options) {
+  window.scroll(0,0);
+
+  var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
+  numDraws = 0,
+  doc = element.ownerDocument,
+  Util = _html2canvas.Util,
+  support = Util.Support(options, doc),
+  ignoreElementsRegExp = new RegExp("(" + options.ignoreElements + ")"),
+  body = doc.body,
+  getCSS = Util.getCSS,
+  pseudoHide = "___html2canvas___pseudoelement",
+  hidePseudoElements = doc.createElement('style');
+
+  hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
+  '.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }';
+
+  body.appendChild(hidePseudoElements);
+
+  images = images || {};
+
+  function documentWidth () {
+    return Math.max(
+      Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
+      Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
+      Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
+      );
+  }
+
+  function documentHeight () {
+    return Math.max(
+      Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
+      Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
+      Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
+      );
+  }
+
+  function getCSSInt(element, attribute) {
+    var val = parseInt(getCSS(element, attribute), 10);
+    return (isNaN(val)) ? 0 : val; // borders in old IE are throwing 'medium' for demo.html
+  }
+
+  function renderRect (ctx, x, y, w, h, bgcolor) {
+    if (bgcolor !== "transparent"){
+      ctx.setVariable("fillStyle", bgcolor);
+      ctx.fillRect(x, y, w, h);
+      numDraws+=1;
+    }
+  }
+
+  function capitalize(m, p1, p2) {
+    if (m.length > 0) {
+      return p1 + p2.toUpperCase();
+    }
+  }
+
+  function textTransform (text, transform) {
+    switch(transform){
+      case "lowercase":
+        return text.toLowerCase();
+      case "capitalize":
+        return text.replace( /(^|\s|:|-|\(|\))([a-z])/g, capitalize);
+      case "uppercase":
+        return text.toUpperCase();
+      default:
+        return text;
+    }
+  }
+
+  function noLetterSpacing(letter_spacing) {
+    return (/^(normal|none|0px)$/.test(letter_spacing));
+  }
+
+  function drawText(currentText, x, y, ctx){
+    if (currentText !== null && Util.trimText(currentText).length > 0) {
+      ctx.fillText(currentText, x, y);
+      numDraws+=1;
+    }
+  }
+
+  function setTextVariables(ctx, el, text_decoration, color) {
+    var align = false,
+    bold = getCSS(el, "fontWeight"),
+    family = getCSS(el, "fontFamily"),
+    size = getCSS(el, "fontSize"),
+    shadows = Util.parseTextShadows(getCSS(el, "textShadow"));
+
+    switch(parseInt(bold, 10)){
+      case 401:
+        bold = "bold";
+        break;
+      case 400:
+        bold = "normal";
+        break;
+    }
+
+    ctx.setVariable("fillStyle", color);
+    ctx.setVariable("font", [getCSS(el, "fontStyle"), getCSS(el, "fontVariant"), bold, size, family].join(" "));
+    ctx.setVariable("textAlign", (align) ? "right" : "left");
+
+    if (shadows.length) {
+      // TODO: support multiple text shadows
+      // apply the first text shadow
+      ctx.setVariable("shadowColor", shadows[0].color);
+      ctx.setVariable("shadowOffsetX", shadows[0].offsetX);
+      ctx.setVariable("shadowOffsetY", shadows[0].offsetY);
+      ctx.setVariable("shadowBlur", shadows[0].blur);
+    }
+
+    if (text_decoration !== "none"){
+      return Util.Font(family, size, doc);
+    }
+  }
+
+  function renderTextDecoration(ctx, text_decoration, bounds, metrics, color) {
+    switch(text_decoration) {
+      case "underline":
+        // Draws a line at the baseline of the font
+        // TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size
+        renderRect(ctx, bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, color);
+        break;
+      case "overline":
+        renderRect(ctx, bounds.left, Math.round(bounds.top), bounds.width, 1, color);
+        break;
+      case "line-through":
+        // TODO try and find exact position for line-through
+        renderRect(ctx, bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, color);
+        break;
+    }
+  }
+
+  function getTextBounds(state, text, textDecoration, isLast, transform) {
+    var bounds;
+    if (support.rangeBounds && !transform) {
+      if (textDecoration !== "none" || Util.trimText(text).length !== 0) {
+        bounds = textRangeBounds(text, state.node, state.textOffset);
+      }
+      state.textOffset += text.length;
+    } else if (state.node && typeof state.node.nodeValue === "string" ){
+      var newTextNode = (isLast) ? state.node.splitText(text.length) : null;
+      bounds = textWrapperBounds(state.node, transform);
+      state.node = newTextNode;
+    }
+    return bounds;
+  }
+
+  function textRangeBounds(text, textNode, textOffset) {
+    var range = doc.createRange();
+    range.setStart(textNode, textOffset);
+    range.setEnd(textNode, textOffset + text.length);
+    return range.getBoundingClientRect();
+  }
+
+  function textWrapperBounds(oldTextNode, transform) {
+    var parent = oldTextNode.parentNode,
+    wrapElement = doc.createElement('wrapper'),
+    backupText = oldTextNode.cloneNode(true);
+
+    wrapElement.appendChild(oldTextNode.cloneNode(true));
+    parent.replaceChild(wrapElement, oldTextNode);
+
+    var bounds = transform ? Util.OffsetBounds(wrapElement) : Util.Bounds(wrapElement);
+    parent.replaceChild(backupText, wrapElement);
+    return bounds;
+  }
+
+  function renderText(el, textNode, stack) {
+    var ctx = stack.ctx,
+    color = getCSS(el, "color"),
+    textDecoration = getCSS(el, "textDecoration"),
+    textAlign = getCSS(el, "textAlign"),
+    metrics,
+    textList,
+    state = {
+      node: textNode,
+      textOffset: 0
+    };
+
+    if (Util.trimText(textNode.nodeValue).length > 0) {
+      textNode.nodeValue = textTransform(textNode.nodeValue, getCSS(el, "textTransform"));
+      textAlign = textAlign.replace(["-webkit-auto"],["auto"]);
+
+      textList = (!options.letterRendering && /^(left|right|justify|auto)$/.test(textAlign) && noLetterSpacing(getCSS(el, "letterSpacing"))) ?
+      textNode.nodeValue.split(/(\b| )/)
+      : textNode.nodeValue.split("");
+
+      metrics = setTextVariables(ctx, el, textDecoration, color);
+
+      if (options.chinese) {
+        textList.forEach(function(word, index) {
+          if (/.*[\u4E00-\u9FA5].*$/.test(word)) {
+            word = word.split("");
+            word.unshift(index, 1);
+            textList.splice.apply(textList, word);
+          }
+        });
+      }
+
+      textList.forEach(function(text, index) {
+        var bounds = getTextBounds(state, text, textDecoration, (index < textList.length - 1), stack.transform.matrix);
+        if (bounds) {
+          drawText(text, bounds.left, bounds.bottom, ctx);
+          renderTextDecoration(ctx, textDecoration, bounds, metrics, color);
+        }
+      });
+    }
+  }
+
+  function listPosition (element, val) {
+    var boundElement = doc.createElement( "boundelement" ),
+    originalType,
+    bounds;
+
+    boundElement.style.display = "inline";
+
+    originalType = element.style.listStyleType;
+    element.style.listStyleType = "none";
+
+    boundElement.appendChild(doc.createTextNode(val));
+
+    element.insertBefore(boundElement, element.firstChild);
+
+    bounds = Util.Bounds(boundElement);
+    element.removeChild(boundElement);
+    element.style.listStyleType = originalType;
+    return bounds;
+  }
+
+  function elementIndex(el) {
+    var i = -1,
+    count = 1,
+    childs = el.parentNode.childNodes;
+
+    if (el.parentNode) {
+      while(childs[++i] !== el) {
+        if (childs[i].nodeType === 1) {
+          count++;
+        }
+      }
+      return count;
+    } else {
+      return -1;
+    }
+  }
+
+  function listItemText(element, type) {
+    var currentIndex = elementIndex(element), text;
+    switch(type){
+      case "decimal":
+        text = currentIndex;
+        break;
+      case "decimal-leading-zero":
+        text = (currentIndex.toString().length === 1) ? currentIndex = "0" + currentIndex.toString() : currentIndex.toString();
+        break;
+      case "upper-roman":
+        text = _html2canvas.Generate.ListRoman( currentIndex );
+        break;
+      case "lower-roman":
+        text = _html2canvas.Generate.ListRoman( currentIndex ).toLowerCase();
+        break;
+      case "lower-alpha":
+        text = _html2canvas.Generate.ListAlpha( currentIndex ).toLowerCase();
+        break;
+      case "upper-alpha":
+        text = _html2canvas.Generate.ListAlpha( currentIndex );
+        break;
+    }
+
+    return text + ". ";
+  }
+
+  function renderListItem(element, stack, elBounds) {
+    var x,
+    text,
+    ctx = stack.ctx,
+    type = getCSS(element, "listStyleType"),
+    listBounds;
+
+    if (/^(decimal|decimal-leading-zero|upper-alpha|upper-latin|upper-roman|lower-alpha|lower-greek|lower-latin|lower-roman)$/i.test(type)) {
+      text = listItemText(element, type);
+      listBounds = listPosition(element, text);
+      setTextVariables(ctx, element, "none", getCSS(element, "color"));
+
+      if (getCSS(element, "listStylePosition") === "inside") {
+        ctx.setVariable("textAlign", "left");
+        x = elBounds.left;
+      } else {
+        return;
+      }
+
+      drawText(text, x, listBounds.bottom, ctx);
+    }
+  }
+
+  function loadImage (src){
+    var img = images[src];
+    return (img && img.succeeded === true) ? img.img : false;
+  }
+
+  function clipBounds(src, dst){
+    var x = Math.max(src.left, dst.left),
+    y = Math.max(src.top, dst.top),
+    x2 = Math.min((src.left + src.width), (dst.left + dst.width)),
+    y2 = Math.min((src.top + src.height), (dst.top + dst.height));
+
+    return {
+      left:x,
+      top:y,
+      width:x2-x,
+      height:y2-y
+    };
+  }
+
+  function setZ(element, stack, parentStack){
+    var newContext,
+    isPositioned = stack.cssPosition !== 'static',
+    zIndex = isPositioned ? getCSS(element, 'zIndex') : 'auto',
+    opacity = getCSS(element, 'opacity'),
+    isFloated = getCSS(element, 'cssFloat') !== 'none';
+
+    // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
+    // When a new stacking context should be created:
+    // the root element (HTML),
+    // positioned (absolutely or relatively) with a z-index value other than "auto",
+    // elements with an opacity value less than 1. (See the specification for opacity),
+    // on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post)
+
+    stack.zIndex = newContext = h2czContext(zIndex);
+    newContext.isPositioned = isPositioned;
+    newContext.isFloated = isFloated;
+    newContext.opacity = opacity;
+    newContext.ownStacking = (zIndex !== 'auto' || opacity < 1);
+
+    if (parentStack) {
+      parentStack.zIndex.children.push(stack);
+    }
+  }
+
+  function renderImage(ctx, element, image, bounds, borders) {
+
+    var paddingLeft = getCSSInt(element, 'paddingLeft'),
+    paddingTop = getCSSInt(element, 'paddingTop'),
+    paddingRight = getCSSInt(element, 'paddingRight'),
+    paddingBottom = getCSSInt(element, 'paddingBottom');
+
+    drawImage(
+      ctx,
+      image,
+      0, //sx
+      0, //sy
+      image.width, //sw
+      image.height, //sh
+      bounds.left + paddingLeft + borders[3].width, //dx
+      bounds.top + paddingTop + borders[0].width, // dy
+      bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight), //dw
+      bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom) //dh
+      );
+  }
+
+  function getBorderData(element) {
+    return ["Top", "Right", "Bottom", "Left"].map(function(side) {
+      return {
+        width: getCSSInt(element, 'border' + side + 'Width'),
+        color: getCSS(element, 'border' + side + 'Color')
+      };
+    });
+  }
+
+  function getBorderRadiusData(element) {
+    return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
+      return getCSS(element, 'border' + side + 'Radius');
+    });
+  }
+
+  var getCurvePoints = (function(kappa) {
+
+    return function(x, y, r1, r2) {
+      var ox = (r1) * kappa, // control point offset horizontal
+      oy = (r2) * kappa, // control point offset vertical
+      xm = x + r1, // x-middle
+      ym = y + r2; // y-middle
+      return {
+        topLeft: bezierCurve({
+          x:x,
+          y:ym
+        }, {
+          x:x,
+          y:ym - oy
+        }, {
+          x:xm - ox,
+          y:y
+        }, {
+          x:xm,
+          y:y
+        }),
+        topRight: bezierCurve({
+          x:x,
+          y:y
+        }, {
+          x:x + ox,
+          y:y
+        }, {
+          x:xm,
+          y:ym - oy
+        }, {
+          x:xm,
+          y:ym
+        }),
+        bottomRight: bezierCurve({
+          x:xm,
+          y:y
+        }, {
+          x:xm,
+          y:y + oy
+        }, {
+          x:x + ox,
+          y:ym
+        }, {
+          x:x,
+          y:ym
+        }),
+        bottomLeft: bezierCurve({
+          x:xm,
+          y:ym
+        }, {
+          x:xm - ox,
+          y:ym
+        }, {
+          x:x,
+          y:y + oy
+        }, {
+          x:x,
+          y:y
+        })
+      };
+    };
+  })(4 * ((Math.sqrt(2) - 1) / 3));
+
+  function bezierCurve(start, startControl, endControl, end) {
+
+    var lerp = function (a, b, t) {
+      return {
+        x:a.x + (b.x - a.x) * t,
+        y:a.y + (b.y - a.y) * t
+      };
+    };
+
+    return {
+      start: start,
+      startControl: startControl,
+      endControl: endControl,
+      end: end,
+      subdivide: function(t) {
+        var ab = lerp(start, startControl, t),
+        bc = lerp(startControl, endControl, t),
+        cd = lerp(endControl, end, t),
+        abbc = lerp(ab, bc, t),
+        bccd = lerp(bc, cd, t),
+        dest = lerp(abbc, bccd, t);
+        return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
+      },
+      curveTo: function(borderArgs) {
+        borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
+      },
+      curveToReversed: function(borderArgs) {
+        borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
+      }
+    };
+  }
+
+  function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
+    if (radius1[0] > 0 || radius1[1] > 0) {
+      borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
+      corner1[0].curveTo(borderArgs);
+      corner1[1].curveTo(borderArgs);
+    } else {
+      borderArgs.push(["line", x, y]);
+    }
+
+    if (radius2[0] > 0 || radius2[1] > 0) {
+      borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
+    }
+  }
+
+  function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
+    var borderArgs = [];
+
+    if (radius1[0] > 0 || radius1[1] > 0) {
+      borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
+      outer1[1].curveTo(borderArgs);
+    } else {
+      borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
+    }
+
+    if (radius2[0] > 0 || radius2[1] > 0) {
+      borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
+      outer2[0].curveTo(borderArgs);
+      borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
+      inner2[0].curveToReversed(borderArgs);
+    } else {
+      borderArgs.push([ "line", borderData.c2[0], borderData.c2[1]]);
+      borderArgs.push([ "line", borderData.c3[0], borderData.c3[1]]);
+    }
+
+    if (radius1[0] > 0 || radius1[1] > 0) {
+      borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
+      inner1[1].curveToReversed(borderArgs);
+    } else {
+      borderArgs.push([ "line", borderData.c4[0], borderData.c4[1]]);
+    }
+
+    return borderArgs;
+  }
+
+  function calculateCurvePoints(bounds, borderRadius, borders) {
+
+    var x = bounds.left,
+    y = bounds.top,
+    width = bounds.width,
+    height = bounds.height,
+
+    tlh = borderRadius[0][0],
+    tlv = borderRadius[0][1],
+    trh = borderRadius[1][0],
+    trv = borderRadius[1][1],
+    brh = borderRadius[2][0],
+    brv = borderRadius[2][1],
+    blh = borderRadius[3][0],
+    blv = borderRadius[3][1],
+
+    topWidth = width - trh,
+    rightHeight = height - brv,
+    bottomWidth = width - brh,
+    leftHeight = height - blv;
+
+    return {
+      topLeftOuter: getCurvePoints(
+        x,
+        y,
+        tlh,
+        tlv
+        ).topLeft.subdivide(0.5),
+
+      topLeftInner: getCurvePoints(
+        x + borders[3].width,
+        y + borders[0].width,
+        Math.max(0, tlh - borders[3].width),
+        Math.max(0, tlv - borders[0].width)
+        ).topLeft.subdivide(0.5),
+
+      topRightOuter: getCurvePoints(
+        x + topWidth,
+        y,
+        trh,
+        trv
+        ).topRight.subdivide(0.5),
+
+      topRightInner: getCurvePoints(
+        x + Math.min(topWidth, width + borders[3].width),
+        y + borders[0].width,
+        (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width,
+        trv - borders[0].width
+        ).topRight.subdivide(0.5),
+
+      bottomRightOuter: getCurvePoints(
+        x + bottomWidth,
+        y + rightHeight,
+        brh,
+        brv
+        ).bottomRight.subdivide(0.5),
+
+      bottomRightInner: getCurvePoints(
+        x + Math.min(bottomWidth, width + borders[3].width),
+        y + Math.min(rightHeight, height + borders[0].width),
+        Math.max(0, brh - borders[1].width),
+        Math.max(0, brv - borders[2].width)
+        ).bottomRight.subdivide(0.5),
+
+      bottomLeftOuter: getCurvePoints(
+        x,
+        y + leftHeight,
+        blh,
+        blv
+        ).bottomLeft.subdivide(0.5),
+
+      bottomLeftInner: getCurvePoints(
+        x + borders[3].width,
+        y + leftHeight,
+        Math.max(0, blh - borders[3].width),
+        Math.max(0, blv - borders[2].width)
+        ).bottomLeft.subdivide(0.5)
+    };
+  }
+
+  function getBorderClip(element, borderPoints, borders, radius, bounds) {
+    var backgroundClip = getCSS(element, 'backgroundClip'),
+    borderArgs = [];
+
+    switch(backgroundClip) {
+      case "content-box":
+      case "padding-box":
+        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
+        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
+        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
+        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
+        break;
+
+      default:
+        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
+        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
+        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
+        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
+        break;
+    }
+
+    return borderArgs;
+  }
+
+  function parseBorders(element, bounds, borders){
+    var x = bounds.left,
+    y = bounds.top,
+    width = bounds.width,
+    height = bounds.height,
+    borderSide,
+    bx,
+    by,
+    bw,
+    bh,
+    borderArgs,
+    // http://www.w3.org/TR/css3-background/#the-border-radius
+    borderRadius = getBorderRadiusData(element),
+    borderPoints = calculateCurvePoints(bounds, borderRadius, borders),
+    borderData = {
+      clip: getBorderClip(element, borderPoints, borders, borderRadius, bounds),
+      borders: []
+    };
+
+    for (borderSide = 0; borderSide < 4; borderSide++) {
+
+      if (borders[borderSide].width > 0) {
+        bx = x;
+        by = y;
+        bw = width;
+        bh = height - (borders[2].width);
+
+        switch(borderSide) {
+          case 0:
+            // top border
+            bh = borders[0].width;
+
+            borderArgs = drawSide({
+              c1: [bx, by],
+              c2: [bx + bw, by],
+              c3: [bx + bw - borders[1].width, by + bh],
+              c4: [bx + borders[3].width, by + bh]
+            }, borderRadius[0], borderRadius[1],
+            borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
+            break;
+          case 1:
+            // right border
+            bx = x + width - (borders[1].width);
+            bw = borders[1].width;
+
+            borderArgs = drawSide({
+              c1: [bx + bw, by],
+              c2: [bx + bw, by + bh + borders[2].width],
+              c3: [bx, by + bh],
+              c4: [bx, by + borders[0].width]
+            }, borderRadius[1], borderRadius[2],
+            borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
+            break;
+          case 2:
+            // bottom border
+            by = (by + height) - (borders[2].width);
+            bh = borders[2].width;
+
+            borderArgs = drawSide({
+              c1: [bx + bw, by + bh],
+              c2: [bx, by + bh],
+              c3: [bx + borders[3].width, by],
+              c4: [bx + bw - borders[3].width, by]
+            }, borderRadius[2], borderRadius[3],
+            borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
+            break;
+          case 3:
+            // left border
+            bw = borders[3].width;
+
+            borderArgs = drawSide({
+              c1: [bx, by + bh + borders[2].width],
+              c2: [bx, by],
+              c3: [bx + bw, by + borders[0].width],
+              c4: [bx + bw, by + bh]
+            }, borderRadius[3], borderRadius[0],
+            borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
+            break;
+        }
+
+        borderData.borders.push({
+          args: borderArgs,
+          color: borders[borderSide].color
+        });
+
+      }
+    }
+
+    return borderData;
+  }
+
+  function createShape(ctx, args) {
+    var shape = ctx.drawShape();
+    args.forEach(function(border, index) {
+      shape[(index === 0) ? "moveTo" : border[0] + "To" ].apply(null, border.slice(1));
+    });
+    return shape;
+  }
+
+  function renderBorders(ctx, borderArgs, color) {
+    if (color !== "transparent") {
+      ctx.setVariable( "fillStyle", color);
+      createShape(ctx, borderArgs);
+      ctx.fill();
+      numDraws+=1;
+    }
+  }
+
+  function renderFormValue (el, bounds, stack){
+
+    var valueWrap = doc.createElement('valuewrap'),
+    cssPropertyArray = ['lineHeight','textAlign','fontFamily','color','fontSize','paddingLeft','paddingTop','width','height','border','borderLeftWidth','borderTopWidth'],
+    textValue,
+    textNode;
+
+    cssPropertyArray.forEach(function(property) {
+      try {
+        valueWrap.style[property] = getCSS(el, property);
+      } catch(e) {
+        // Older IE has issues with "border"
+        Util.log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
+      }
+    });
+
+    valueWrap.style.borderColor = "black";
+    valueWrap.style.borderStyle = "solid";
+    valueWrap.style.display = "block";
+    valueWrap.style.position = "absolute";
+
+    if (/^(submit|reset|button|text|password)$/.test(el.type) || el.nodeName === "SELECT"){
+      valueWrap.style.lineHeight = getCSS(el, "height");
+    }
+
+    valueWrap.style.top = bounds.top + "px";
+    valueWrap.style.left = bounds.left + "px";
+
+    textValue = (el.nodeName === "SELECT") ? (el.options[el.selectedIndex] || 0).text : el.value;
+    if(!textValue) {
+      textValue = el.placeholder;
+    }
+
+    textNode = doc.createTextNode(textValue);
+
+    valueWrap.appendChild(textNode);
+    body.appendChild(valueWrap);
+
+    renderText(el, textNode, stack);
+    body.removeChild(valueWrap);
+  }
+
+  function drawImage (ctx) {
+    ctx.drawImage.apply(ctx, Array.prototype.slice.call(arguments, 1));
+    numDraws+=1;
+  }
+
+  function getPseudoElement(el, which) {
+    var elStyle = window.getComputedStyle(el, which);
+    if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") {
+      return;
+    }
+    var content = elStyle.content + '',
+    first = content.substr( 0, 1 );
+    //strips quotes
+    if(first === content.substr( content.length - 1 ) && first.match(/'|"/)) {
+      content = content.substr( 1, content.length - 2 );
+    }
+
+    var isImage = content.substr( 0, 3 ) === 'url',
+    elps = document.createElement( isImage ? 'img' : 'span' );
+
+    elps.className = pseudoHide + "-before " + pseudoHide + "-after";
+
+    Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
+      // Prevent assigning of read only CSS Rules, ex. length, parentRule
+      try {
+        elps.style[prop] = elStyle[prop];
+      } catch (e) {
+        Util.log(['Tried to assign readonly property ', prop, 'Error:', e]);
+      }
+    });
+
+    if(isImage) {
+      elps.src = Util.parseBackgroundImage(content)[0].args[0];
+    } else {
+      elps.innerHTML = content;
+    }
+    return elps;
+  }
+
+  function indexedProperty(property) {
+    return (isNaN(window.parseInt(property, 10)));
+  }
+
+  function injectPseudoElements(el, stack) {
+    var before = getPseudoElement(el, ':before'),
+    after = getPseudoElement(el, ':after');
+    if(!before && !after) {
+      return;
+    }
+
+    if(before) {
+      el.className += " " + pseudoHide + "-before";
+      el.parentNode.insertBefore(before, el);
+      parseElement(before, stack, true);
+      el.parentNode.removeChild(before);
+      el.className = el.className.replace(pseudoHide + "-before", "").trim();
+    }
+
+    if (after) {
+      el.className += " " + pseudoHide + "-after";
+      el.appendChild(after);
+      parseElement(after, stack, true);
+      el.removeChild(after);
+      el.className = el.className.replace(pseudoHide + "-after", "").trim();
+    }
+
+  }
+
+  function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
+    var offsetX = Math.round(bounds.left + backgroundPosition.left),
+    offsetY = Math.round(bounds.top + backgroundPosition.top);
+
+    ctx.createPattern(image);
+    ctx.translate(offsetX, offsetY);
+    ctx.fill();
+    ctx.translate(-offsetX, -offsetY);
+  }
+
+  function backgroundRepeatShape(ctx, image, backgroundPosition, bounds, left, top, width, height) {
+    var args = [];
+    args.push(["line", Math.round(left), Math.round(top)]);
+    args.push(["line", Math.round(left + width), Math.round(top)]);
+    args.push(["line", Math.round(left + width), Math.round(height + top)]);
+    args.push(["line", Math.round(left), Math.round(height + top)]);
+    createShape(ctx, args);
+    ctx.save();
+    ctx.clip();
+    renderBackgroundRepeat(ctx, image, backgroundPosition, bounds);
+    ctx.restore();
+  }
+
+  function renderBackgroundColor(ctx, backgroundBounds, bgcolor) {
+    renderRect(
+      ctx,
+      backgroundBounds.left,
+      backgroundBounds.top,
+      backgroundBounds.width,
+      backgroundBounds.height,
+      bgcolor
+      );
+  }
+
+  function renderBackgroundRepeating(el, bounds, ctx, image, imageIndex) {
+    var backgroundSize = Util.BackgroundSize(el, bounds, image, imageIndex),
+    backgroundPosition = Util.BackgroundPosition(el, bounds, image, imageIndex, backgroundSize),
+    backgroundRepeat = getCSS(el, "backgroundRepeat").split(",").map(Util.trimText);
+
+    image = resizeImage(image, backgroundSize);
+
+    backgroundRepeat = backgroundRepeat[imageIndex] || backgroundRepeat[0];
+
+    switch (backgroundRepeat) {
+      case "repeat-x":
+        backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
+          bounds.left, bounds.top + backgroundPosition.top, 99999, image.height);
+        break;
+
+      case "repeat-y":
+        backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
+          bounds.left + backgroundPosition.left, bounds.top, image.width, 99999);
+        break;
+
+      case "no-repeat":
+        backgroundRepeatShape(ctx, image, backgroundPosition, bounds,
+          bounds.left + backgroundPosition.left, bounds.top + backgroundPosition.top, image.width, image.height);
+        break;
+
+      default:
+        renderBackgroundRepeat(ctx, image, backgroundPosition, {
+          top: bounds.top,
+          left: bounds.left,
+          width: image.width,
+          height: image.height
+        });
+        break;
+    }
+  }
+
+  function renderBackgroundImage(element, bounds, ctx) {
+    var backgroundImage = getCSS(element, "backgroundImage"),
+    backgroundImages = Util.parseBackgroundImage(backgroundImage),
+    image,
+    imageIndex = backgroundImages.length;
+
+    while(imageIndex--) {
+      backgroundImage = backgroundImages[imageIndex];
+
+      if (!backgroundImage.args || backgroundImage.args.length === 0) {
+        continue;
+      }
+
+      var key = backgroundImage.method === 'url' ?
+      backgroundImage.args[0] :
+      backgroundImage.value;
+
+      image = loadImage(key);
+
+      // TODO add support for background-origin
+      if (image) {
+        renderBackgroundRepeating(element, bounds, ctx, image, imageIndex);
+      } else {
+        Util.log("html2canvas: Error loading background:", backgroundImage);
+      }
+    }
+  }
+
+  function resizeImage(image, bounds) {
+    if(image.width === bounds.width && image.height === bounds.height) {
+      return image;
+    }
+
+    var ctx, canvas = doc.createElement('canvas');
+    canvas.width = bounds.width;
+    canvas.height = bounds.height;
+    ctx = canvas.getContext("2d");
+    drawImage(ctx, image, 0, 0, image.width, image.height, 0, 0, bounds.width, bounds.height );
+    return canvas;
+  }
+
+  function setOpacity(ctx, element, parentStack) {
+    return ctx.setVariable("globalAlpha", getCSS(element, "opacity") * ((parentStack) ? parentStack.opacity : 1));
+  }
+
+  function removePx(str) {
+    return str.replace("px", "");
+  }
+
+  var transformRegExp = /(matrix)\((.+)\)/;
+
+  function getTransform(element, parentStack) {
+    var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
+    var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";
+
+    transformOrigin = transformOrigin.split(" ").map(removePx).map(Util.asFloat);
+
+    var matrix;
+    if (transform && transform !== "none") {
+      var match = transform.match(transformRegExp);
+      if (match) {
+        switch(match[1]) {
+          case "matrix":
+            matrix = match[2].split(",").map(Util.trimText).map(Util.asFloat);
+            break;
+        }
+      }
+    }
+
+    return {
+      origin: transformOrigin,
+      matrix: matrix
+    };
+  }
+
+  function createStack(element, parentStack, bounds, transform) {
+    var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height),
+    stack = {
+      ctx: ctx,
+      opacity: setOpacity(ctx, element, parentStack),
+      cssPosition: getCSS(element, "position"),
+      borders: getBorderData(element),
+      transform: transform,
+      clip: (parentStack && parentStack.clip) ? Util.Extend( {}, parentStack.clip ) : null
+    };
+
+    setZ(element, stack, parentStack);
+
+    // TODO correct overflow for absolute content residing under a static position
+    if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){
+      stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds;
+    }
+
+    return stack;
+  }
+
+  function getBackgroundBounds(borders, bounds, clip) {
+    var backgroundBounds = {
+      left: bounds.left + borders[3].width,
+      top: bounds.top + borders[0].width,
+      width: bounds.width - (borders[1].width + borders[3].width),
+      height: bounds.height - (borders[0].width + borders[2].width)
+    };
+
+    if (clip) {
+      backgroundBounds = clipBounds(backgroundBounds, clip);
+    }
+
+    return backgroundBounds;
+  }
+
+  function getBounds(element, transform) {
+    var bounds = (transform.matrix) ? Util.OffsetBounds(element) : Util.Bounds(element);
+    transform.origin[0] += bounds.left;
+    transform.origin[1] += bounds.top;
+    return bounds;
+  }
+
+  function renderElement(element, parentStack, pseudoElement, ignoreBackground) {
+    var transform = getTransform(element, parentStack),
+    bounds = getBounds(element, transform),
+    image,
+    stack = createStack(element, parentStack, bounds, transform),
+    borders = stack.borders,
+    ctx = stack.ctx,
+    backgroundBounds = getBackgroundBounds(borders, bounds, stack.clip),
+    borderData = parseBorders(element, bounds, borders),
+    backgroundColor = (ignoreElementsRegExp.test(element.nodeName)) ? "#efefef" : getCSS(element, "backgroundColor");
+
+
+    createShape(ctx, borderData.clip);
+
+    ctx.save();
+    ctx.clip();
+
+    if (backgroundBounds.height > 0 && backgroundBounds.width > 0 && !ignoreBackground) {
+      renderBackgroundColor(ctx, bounds, backgroundColor);
+      renderBackgroundImage(element, backgroundBounds, ctx);
+    } else if (ignoreBackground) {
+      stack.backgroundColor =  backgroundColor;
+    }
+
+    ctx.restore();
+
+    borderData.borders.forEach(function(border) {
+      renderBorders(ctx, border.args, border.color);
+    });
+
+    if (!pseudoElement) {
+      injectPseudoElements(element, stack);
+    }
+
+    switch(element.nodeName){
+      case "IMG":
+        if ((image = loadImage(element.getAttribute('src')))) {
+          renderImage(ctx, element, image, bounds, borders);
+        } else {
+          Util.log("html2canvas: Error loading <img>:" + element.getAttribute('src'));
+        }
+        break;
+      case "INPUT":
+        // TODO add all relevant type's, i.e. HTML5 new stuff
+        // todo add support for placeholder attribute for browsers which support it
+        if (/^(text|url|email|submit|button|reset)$/.test(element.type) && (element.value || element.placeholder || "").length > 0){
+          renderFormValue(element, bounds, stack);
+        }
+        break;
+      case "TEXTAREA":
+        if ((element.value || element.placeholder || "").length > 0){
+          renderFormValue(element, bounds, stack);
+        }
+        break;
+      case "SELECT":
+        if ((element.options||element.placeholder || "").length > 0){
+          renderFormValue(element, bounds, stack);
+        }
+        break;
+      case "LI":
+        renderListItem(element, stack, backgroundBounds);
+        break;
+      case "CANVAS":
+        renderImage(ctx, element, element, bounds, borders);
+        break;
+    }
+
+    return stack;
+  }
+
+  function isElementVisible(element) {
+    return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
+  }
+
+  function parseElement (element, stack, pseudoElement) {
+    if (isElementVisible(element)) {
+      stack = renderElement(element, stack, pseudoElement, false) || stack;
+      if (!ignoreElementsRegExp.test(element.nodeName)) {
+        parseChildren(element, stack, pseudoElement);
+      }
+    }
+  }
+
+  function parseChildren(element, stack, pseudoElement) {
+    Util.Children(element).forEach(function(node) {
+      if (node.nodeType === node.ELEMENT_NODE) {
+        parseElement(node, stack, pseudoElement);
+      } else if (node.nodeType === node.TEXT_NODE) {
+        renderText(element, node, stack);
+      }
+    });
+  }
+
+  function init() {
+    var background = getCSS(document.documentElement, "backgroundColor"),
+      transparentBackground = (Util.isTransparent(background) && element === document.body),
+      stack = renderElement(element, null, false, transparentBackground);
+    parseChildren(element, stack);
+
+    if (transparentBackground) {
+      background = stack.backgroundColor;
+    }
+
+    body.removeChild(hidePseudoElements);
+    return {
+      backgroundColor: background,
+      stack: stack
+    };
+  }
+
+  return init();
+};
+
+function h2czContext(zindex) {
+  return {
+    zindex: zindex,
+    children: []
+  };
+}
+
+_html2canvas.Preload = function( options ) {
+
+  var images = {
+    numLoaded: 0,   // also failed are counted here
+    numFailed: 0,
+    numTotal: 0,
+    cleanupDone: false
+  },
+  pageOrigin,
+  Util = _html2canvas.Util,
+  methods,
+  i,
+  count = 0,
+  element = options.elements[0] || document.body,
+  doc = element.ownerDocument,
+  domImages = element.getElementsByTagName('img'), // Fetch images of the present element only
+  imgLen = domImages.length,
+  link = doc.createElement("a"),
+  supportCORS = (function( img ){
+    return (img.crossOrigin !== undefined);
+  })(new Image()),
+  timeoutTimer;
+
+  link.href = window.location.href;
+  pageOrigin  = link.protocol + link.host;
+
+  function isSameOrigin(url){
+    link.href = url;
+    link.href = link.href; // YES, BELIEVE IT OR NOT, that is required for IE9 - http://jsfiddle.net/niklasvh/2e48b/
+    var origin = link.protocol + link.host;
+    return (origin === pageOrigin);
+  }
+
+  function start(){
+    Util.log("html2canvas: start: images: " + images.numLoaded + " / " + images.numTotal + " (failed: " + images.numFailed + ")");
+    if (!images.firstRun && images.numLoaded >= images.numTotal){
+      Util.log("Finished loading images: # " + images.numTotal + " (failed: " + images.numFailed + ")");
+
+      if (typeof options.complete === "function"){
+        options.complete(images);
+      }
+
+    }
+  }
+
+  // TODO modify proxy to serve images with CORS enabled, where available
+  function proxyGetImage(url, img, imageObj){
+    var callback_name,
+    scriptUrl = options.proxy,
+    script;
+
+    link.href = url;
+    url = link.href; // work around for pages with base href="" set - WARNING: this may change the url
+
+    callback_name = 'html2canvas_' + (count++);
+    imageObj.callbackname = callback_name;
+
+    if (scriptUrl.indexOf("?") > -1) {
+      scriptUrl += "&";
+    } else {
+      scriptUrl += "?";
+    }
+    scriptUrl += 'url=' + encodeURIComponent(url) + '&callback=' + callback_name;
+    script = doc.createElement("script");
+
+    window[callback_name] = function(a){
+      if (a.substring(0,6) === "error:"){
+        imageObj.succeeded = false;
+        images.numLoaded++;
+        images.numFailed++;
+        start();
+      } else {
+        setImageLoadHandlers(img, imageObj);
+        img.src = a;
+      }
+      window[callback_name] = undefined; // to work with IE<9  // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
+      try {
+        delete window[callback_name];  // for all browser that support this
+      } catch(ex) {}
+      script.parentNode.removeChild(script);
+      script = null;
+      delete imageObj.script;
+      delete imageObj.callbackname;
+    };
+
+    script.setAttribute("type", "text/javascript");
+    script.setAttribute("src", scriptUrl);
+    imageObj.script = script;
+    window.document.body.appendChild(script);
+
+  }
+
+  function loadPseudoElement(element, type) {
+    var style = window.getComputedStyle(element, type),
+    content = style.content;
+    if (content.substr(0, 3) === 'url') {
+      methods.loadImage(_html2canvas.Util.parseBackgroundImage(content)[0].args[0]);
+    }
+    loadBackgroundImages(style.backgroundImage, element);
+  }
+
+  function loadPseudoElementImages(element) {
+    loadPseudoElement(element, ":before");
+    loadPseudoElement(element, ":after");
+  }
+
+  function loadGradientImage(backgroundImage, bounds) {
+    var img = _html2canvas.Generate.Gradient(backgroundImage, bounds);
+
+    if (img !== undefined){
+      images[backgroundImage] = {
+        img: img,
+        succeeded: true
+      };
+      images.numTotal++;
+      images.numLoaded++;
+      start();
+    }
+  }
+
+  function invalidBackgrounds(background_image) {
+    return (background_image && background_image.method && background_image.args && background_image.args.length > 0 );
+  }
+
+  function loadBackgroundImages(background_image, el) {
+    var bounds;
+
+    _html2canvas.Util.parseBackgroundImage(background_image).filter(invalidBackgrounds).forEach(function(background_image) {
+      if (background_image.method === 'url') {
+        methods.loadImage(background_image.args[0]);
+      } else if(background_image.method.match(/\-?gradient$/)) {
+        if(bounds === undefined) {
+          bounds = _html2canvas.Util.Bounds(el);
+        }
+        loadGradientImage(background_image.value, bounds);
+      }
+    });
+  }
+
+  function getImages (el) {
+    var elNodeType = false;
+
+    // Firefox fails with permission denied on pages with iframes
+    try {
+      Util.Children(el).forEach(getImages);
+    }
+    catch( e ) {}
+
+    try {
+      elNodeType = el.nodeType;
+    } catch (ex) {
+      elNodeType = false;
+      Util.log("html2canvas: failed to access some element's nodeType - Exception: " + ex.message);
+    }
+
+    if (elNodeType === 1 || elNodeType === undefined) {
+      loadPseudoElementImages(el);
+      try {
+        loadBackgroundImages(Util.getCSS(el, 'backgroundImage'), el);
+      } catch(e) {
+        Util.log("html2canvas: failed to get background-image - Exception: " + e.message);
+      }
+      loadBackgroundImages(el);
+    }
+  }
+
+  function setImageLoadHandlers(img, imageObj) {
+    img.onload = function() {
+      if ( imageObj.timer !== undefined ) {
+        // CORS succeeded
+        window.clearTimeout( imageObj.timer );
+      }
+
+      images.numLoaded++;
+      imageObj.succeeded = true;
+      img.onerror = img.onload = null;
+      start();
+    };
+    img.onerror = function() {
+      if (img.crossOrigin === "anonymous") {
+        // CORS failed
+        window.clearTimeout( imageObj.timer );
+
+        // let's try with proxy instead
+        if ( options.proxy ) {
+          var src = img.src;
+          img = new Image();
+          imageObj.img = img;
+          img.src = src;
+
+          proxyGetImage( img.src, img, imageObj );
+          return;
+        }
+      }
+
+      images.numLoaded++;
+      images.numFailed++;
+      imageObj.succeeded = false;
+      img.onerror = img.onload = null;
+      start();
+    };
+  }
+
+  methods = {
+    loadImage: function( src ) {
+      var img, imageObj;
+      if ( src && images[src] === undefined ) {
+        img = new Image();
+        if ( src.match(/data:image\/.*;base64,/i) ) {
+          img.src = src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, '');
+          imageObj = images[src] = {
+            img: img
+          };
+          images.numTotal++;
+          setImageLoadHandlers(img, imageObj);
+        } else if ( isSameOrigin( src ) || options.allowTaint ===  true ) {
+          imageObj = images[src] = {
+            img: img
+          };
+          images.numTotal++;
+          setImageLoadHandlers(img, imageObj);
+          img.src = src;
+        } else if ( supportCORS && !options.allowTaint && options.useCORS ) {
+          // attempt to load with CORS
+
+          img.crossOrigin = "anonymous";
+          imageObj = images[src] = {
+            img: img
+          };
+          images.numTotal++;
+          setImageLoadHandlers(img, imageObj);
+          img.src = src;
+        } else if ( options.proxy ) {
+          imageObj = images[src] = {
+            img: img
+          };
+          images.numTotal++;
+          proxyGetImage( src, img, imageObj );
+        }
+      }
+
+    },
+    cleanupDOM: function(cause) {
+      var img, src;
+      if (!images.cleanupDone) {
+        if (cause && typeof cause === "string") {
+          Util.log("html2canvas: Cleanup because: " + cause);
+        } else {
+          Util.log("html2canvas: Cleanup after timeout: " + options.timeout + " ms.");
+        }
+
+        for (src in images) {
+          if (images.hasOwnProperty(src)) {
+            img = images[src];
+            if (typeof img === "object" && img.callbackname && img.succeeded === undefined) {
+              // cancel proxy image request
+              window[img.callbackname] = undefined; // to work with IE<9  // NOTE: that the undefined callback property-name still exists on the window object (for IE<9)
+              try {
+                delete window[img.callbackname];  // for all browser that support this
+              } catch(ex) {}
+              if (img.script && img.script.parentNode) {
+                img.script.setAttribute("src", "about:blank");  // try to cancel running request
+                img.script.parentNode.removeChild(img.script);
+              }
+              images.numLoaded++;
+              images.numFailed++;
+              Util.log("html2canvas: Cleaned up failed img: '" + src + "' Steps: " + images.numLoaded + " / " + images.numTotal);
+            }
+          }
+        }
+
+        // cancel any pending requests
+        if(window.stop !== undefined) {
+          window.stop();
+        } else if(document.execCommand !== undefined) {
+          document.execCommand("Stop", false);
+        }
+        if (document.close !== undefined) {
+          document.close();
+        }
+        images.cleanupDone = true;
+        if (!(cause && typeof cause === "string")) {
+          start();
+        }
+      }
+    },
+
+    renderingDone: function() {
+      if (timeoutTimer) {
+        window.clearTimeout(timeoutTimer);
+      }
+    }
+  };
+
+  if (options.timeout > 0) {
+    timeoutTimer = window.setTimeout(methods.cleanupDOM, options.timeout);
+  }
+
+  Util.log('html2canvas: Preload starts: finding background-images');
+  images.firstRun = true;
+
+  getImages(element);
+
+  Util.log('html2canvas: Preload: Finding images');
+  // load <img> images
+  for (i = 0; i < imgLen; i+=1){
+    methods.loadImage( domImages[i].getAttribute( "src" ) );
+  }
+
+  images.firstRun = false;
+  Util.log('html2canvas: Preload: Done.');
+  if (images.numTotal === images.numLoaded) {
+    start();
+  }
+
+  return methods;
+};
+
+_html2canvas.Renderer = function(parseQueue, options){
+
+  // http://www.w3.org/TR/CSS21/zindex.html
+  function createRenderQueue(parseQueue) {
+    var queue = [],
+    rootContext;
+
+    rootContext = (function buildStackingContext(rootNode) {
+      var rootContext = {};
+      function insert(context, node, specialParent) {
+        var zi = (node.zIndex.zindex === 'auto') ? 0 : Number(node.zIndex.zindex),
+        contextForChildren = context, // the stacking context for children
+        isPositioned = node.zIndex.isPositioned,
+        isFloated = node.zIndex.isFloated,
+        stub = {node: node},
+        childrenDest = specialParent; // where children without z-index should be pushed into
+
+        if (node.zIndex.ownStacking) {
+          // '!' comes before numbers in sorted array
+          contextForChildren = stub.context = { '!': [{node:node, children: []}]};
+          childrenDest = undefined;
+        } else if (isPositioned || isFloated) {
+          childrenDest = stub.children = [];
+        }
+
+        if (zi === 0 && specialParent) {
+          specialParent.push(stub);
+        } else {
+          if (!context[zi]) { context[zi] = []; }
+          context[zi].push(stub);
+        }
+
+        node.zIndex.children.forEach(function(childNode) {
+          insert(contextForChildren, childNode, childrenDest);
+        });
+      }
+      insert(rootContext, rootNode);
+      return rootContext;
+    })(parseQueue);
+
+    function sortZ(context) {
+      Object.keys(context).sort().forEach(function(zi) {
+        var nonPositioned = [],
+        floated = [],
+        positioned = [],
+        list = [];
+
+        // positioned after static
+        context[zi].forEach(function(v) {
+          if (v.node.zIndex.isPositioned || v.node.zIndex.opacity < 1) {
+            // http://www.w3.org/TR/css3-color/#transparency
+            // non-positioned element with opactiy < 1 should be stacked as if it were a positioned element with â€˜z-index: 0’ and â€˜opacity: 1’.
+            positioned.push(v);
+          } else if (v.node.zIndex.isFloated) {
+            floated.push(v);
+          } else {
+            nonPositioned.push(v);
+          }
+        });
+
+        (function walk(arr) {
+          arr.forEach(function(v) {
+            list.push(v);
+            if (v.children) { walk(v.children); }
+          });
+        })(nonPositioned.concat(floated, positioned));
+
+        list.forEach(function(v) {
+          if (v.context) {
+            sortZ(v.context);
+          } else {
+            queue.push(v.node);
+          }
+        });
+      });
+    }
+
+    sortZ(rootContext);
+
+    return queue;
+  }
+
+  function getRenderer(rendererName) {
+    var renderer;
+
+    if (typeof options.renderer === "string" && _html2canvas.Renderer[rendererName] !== undefined) {
+      renderer = _html2canvas.Renderer[rendererName](options);
+    } else if (typeof rendererName === "function") {
+      renderer = rendererName(options);
+    } else {
+      throw new Error("Unknown renderer");
+    }
+
+    if ( typeof renderer !== "function" ) {
+      throw new Error("Invalid renderer defined");
+    }
+    return renderer;
+  }
+
+  return getRenderer(options.renderer)(parseQueue, options, document, createRenderQueue(parseQueue.stack), _html2canvas);
+};
+
+_html2canvas.Util.Support = function (options, doc) {
+
+  function supportSVGRendering() {
+    var img = new Image(),
+    canvas = doc.createElement("canvas"),
+    ctx = (canvas.getContext === undefined) ? false : canvas.getContext("2d");
+    if (ctx === false) {
+      return false;
+    }
+    canvas.width = canvas.height = 10;
+    img.src = [
+    "data:image/svg+xml,",
+    "<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'>",
+    "<foreignObject width='10' height='10'>",
+    "<div xmlns='http://www.w3.org/1999/xhtml' style='width:10;height:10;'>",
+    "sup",
+    "</div>",
+    "</foreignObject>",
+    "</svg>"
+    ].join("");
+    try {
+      ctx.drawImage(img, 0, 0);
+      canvas.toDataURL();
+    } catch(e) {
+      return false;
+    }
+    _html2canvas.Util.log('html2canvas: Parse: SVG powered rendering available');
+    return true;
+  }
+
+  // Test whether we can use ranges to measure bounding boxes
+  // Opera doesn't provide valid bounds.height/bottom even though it supports the method.
+
+  function supportRangeBounds() {
+    var r, testElement, rangeBounds, rangeHeight, support = false;
+
+    if (doc.createRange) {
+      r = doc.createRange();
+      if (r.getBoundingClientRect) {
+        testElement = doc.createElement('boundtest');
+        testElement.style.height = "123px";
+        testElement.style.display = "block";
+        doc.body.appendChild(testElement);
+
+        r.selectNode(testElement);
+        rangeBounds = r.getBoundingClientRect();
+        rangeHeight = rangeBounds.height;
+
+        if (rangeHeight === 123) {
+          support = true;
+        }
+        doc.body.removeChild(testElement);
+      }
+    }
+
+    return support;
+  }
+
+  return {
+    rangeBounds: supportRangeBounds(),
+    svgRendering: options.svgRendering && supportSVGRendering()
+  };
+};
+window.html2canvas = function(elements, opts) {
+  elements = (elements.length) ? elements : [elements];
+  var queue,
+  canvas,
+  options = {
+    // general
+    logging: false,
+    elements: elements,
+    background: "#fff",
+
+    // preload options
+    proxy: null,
+    timeout: 0,    // no timeout
+    useCORS: false, // try to load images as CORS (where available), before falling back to proxy
+    allowTaint: false, // whether to allow images to taint the canvas, won't need proxy if set to true
+
+    // parse options
+    svgRendering: false, // use svg powered rendering where available (FF11+)
+    ignoreElements: "IFRAME|OBJECT|PARAM",
+    useOverflow: true,
+    letterRendering: false,
+    chinese: false,
+
+    // render options
+
+    width: null,
+    height: null,
+    taintTest: true, // do a taint test with all images before applying to canvas
+    renderer: "Canvas"
+  };
+
+  options = _html2canvas.Util.Extend(opts, options);
+
+  _html2canvas.logging = options.logging;
+  options.complete = function( images ) {
+
+    if (typeof options.onpreloaded === "function") {
+      if ( options.onpreloaded( images ) === false ) {
+        return;
+      }
+    }
+    queue = _html2canvas.Parse( images, options );
+
+    if (typeof options.onparsed === "function") {
+      if ( options.onparsed( queue ) === false ) {
+        return;
+      }
+    }
+
+    canvas = _html2canvas.Renderer( queue, options );
+
+    if (typeof options.onrendered === "function") {
+      options.onrendered( canvas );
+    }
+
+
+  };
+
+  // for pages without images, we still want this to be async, i.e. return methods before executing
+  window.setTimeout( function(){
+    _html2canvas.Preload( options );
+  }, 0 );
+
+  return {
+    render: function( queue, opts ) {
+      return _html2canvas.Renderer( queue, _html2canvas.Util.Extend(opts, options) );
+    },
+    parse: function( images, opts ) {
+      return _html2canvas.Parse( images, _html2canvas.Util.Extend(opts, options) );
+    },
+    preload: function( opts ) {
+      return _html2canvas.Preload( _html2canvas.Util.Extend(opts, options) );
+    },
+    log: _html2canvas.Util.log
+  };
+};
+
+window.html2canvas.log = _html2canvas.Util.log; // for renderers
+window.html2canvas.Renderer = {
+  Canvas: undefined // We are assuming this will be used
+};
+_html2canvas.Renderer.Canvas = function(options) {
+  options = options || {};
+
+  var doc = document,
+  safeImages = [],
+  testCanvas = document.createElement("canvas"),
+  testctx = testCanvas.getContext("2d"),
+  Util = _html2canvas.Util,
+  canvas = options.canvas || doc.createElement('canvas');
+
+  function createShape(ctx, args) {
+    ctx.beginPath();
+    args.forEach(function(arg) {
+      ctx[arg.name].apply(ctx, arg['arguments']);
+    });
+    ctx.closePath();
+  }
+
+  function safeImage(item) {
+    if (safeImages.indexOf(item['arguments'][0].src ) === -1) {
+      testctx.drawImage(item['arguments'][0], 0, 0);
+      try {
+        testctx.getImageData(0, 0, 1, 1);
+      } catch(e) {
+        testCanvas = doc.createElement("canvas");
+        testctx = testCanvas.getContext("2d");
+        return false;
+      }
+      safeImages.push(item['arguments'][0].src);
+    }
+    return true;
+  }
+
+  function renderItem(ctx, item) {
+    switch(item.type){
+      case "variable":
+        ctx[item.name] = item['arguments'];
+        break;
+      case "function":
+        switch(item.name) {
+          case "createPattern":
+            if (item['arguments'][0].width > 0 && item['arguments'][0].height > 0) {
+              try {
+                ctx.fillStyle = ctx.createPattern(item['arguments'][0], "repeat");
+              }
+              catch(e) {
+                Util.log("html2canvas: Renderer: Error creating pattern", e.message);
+              }
+            }
+            break;
+          case "drawShape":
+            createShape(ctx, item['arguments']);
+            break;
+          case "drawImage":
+            if (item['arguments'][8] > 0 && item['arguments'][7] > 0) {
+              if (!options.taintTest || (options.taintTest && safeImage(item))) {
+                ctx.drawImage.apply( ctx, item['arguments'] );
+              }
+            }
+            break;
+          default:
+            ctx[item.name].apply(ctx, item['arguments']);
+        }
+        break;
+    }
+  }
+
+  return function(parsedData, options, document, queue, _html2canvas) {
+    var ctx = canvas.getContext("2d"),
+    newCanvas,
+    bounds,
+    fstyle,
+    zStack = parsedData.stack;
+
+    canvas.width = canvas.style.width =  options.width || zStack.ctx.width;
+    canvas.height = canvas.style.height = options.height || zStack.ctx.height;
+
+    fstyle = ctx.fillStyle;
+    ctx.fillStyle = (Util.isTransparent(zStack.backgroundColor) && options.background !== undefined) ? options.background : parsedData.backgroundColor;
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+    ctx.fillStyle = fstyle;
+
+    queue.forEach(function(storageContext) {
+      // set common settings for canvas
+      ctx.textBaseline = "bottom";
+      ctx.save();
+
+      if (storageContext.transform.matrix) {
+        ctx.translate(storageContext.transform.origin[0], storageContext.transform.origin[1]);
+        ctx.transform.apply(ctx, storageContext.transform.matrix);
+        ctx.translate(-storageContext.transform.origin[0], -storageContext.transform.origin[1]);
+      }
+
+      if (storageContext.clip){
+        ctx.beginPath();
+        ctx.rect(storageContext.clip.left, storageContext.clip.top, storageContext.clip.width, storageContext.clip.height);
+        ctx.clip();
+      }
+
+      if (storageContext.ctx.storage) {
+        storageContext.ctx.storage.forEach(function(item) {
+          renderItem(ctx, item);
+        });
+      }
+
+      ctx.restore();
+    });
+
+    Util.log("html2canvas: Renderer: Canvas renderer done - returning canvas obj");
+
+    if (options.elements.length === 1) {
+      if (typeof options.elements[0] === "object" && options.elements[0].nodeName !== "BODY") {
+        // crop image to the bounds of selected (single) element
+        bounds = _html2canvas.Util.Bounds(options.elements[0]);
+        newCanvas = document.createElement('canvas');
+        newCanvas.width = Math.ceil(bounds.width);
+        newCanvas.height = Math.ceil(bounds.height);
+        ctx = newCanvas.getContext("2d");
+
+        ctx.drawImage(canvas, bounds.left, bounds.top, bounds.width, bounds.height, 0, 0, bounds.width, bounds.height);
+        canvas = null;
+        return newCanvas;
+      }
+    }
+
+    return canvas;
+  };
+};
+})(window,document);
\ No newline at end of file
index 24b0448..f414a82 100644 (file)
@@ -26,6 +26,43 @@ function toggleControls(running) {
   }
 }
 
+function setSampleRate(id, val, result) {
+  var n = Number(val);
+  if (isNaN(n) || n < 0) {
+    result.text("You must give positive numeric values.").addClass("fail");
+    return;
+  }
+  
+  $.ajax({
+    url : mctx.api + 'sensors',
+    data : {id : id, sample_s : n}
+  }).done(function(data) {
+    if (!result.checkStatus(data)) {
+      return;
+    }
+    
+    result.text("Set ok!").removeClass("fail").addClass("pass");
+  });
+};
+
+$.fn.loadSensorList = function (result, input) {
+  var select = this;
+  select.empty(); //Reset list
+  
+  $.ajax({
+    url : mctx.api + 'identify',
+    data : {'sensors' : 1}
+  }).done(function(data) {
+    if (!result.checkStatus(data)) {
+      return;
+    }
+    for (var id in data.sensors) {
+      var option = $("<option/>", {value : id, text : data.sensors[id].name});
+      select.append(option);
+    }
+  });
+}
+
 $.fn.setStatusUpdater = function () {
   var result = this;
   
@@ -67,13 +104,15 @@ $.fn.setStatusUpdater = function () {
           fail = true;
       }
       
-      if (data.control_state_id !== mctx.control.state) {      
+      if (data.control_state_id !== mctx.control.state) {   
+        //Set logic for sensor sample rate thing
+        $("#sensor-select").loadSensorList($("#samplerate-result"));      
         toggleControls(running);
         $(result).text(text);
         if (fail) {
-          $(result).parent().addClass("fail");
+          $(result).parent().addClass("fail").removeClass("pass");
         } else {
-          $(result).parent().addClass("pass");
+          $(result).parent().addClass("pass").removeClass("fail");
         }
         
         mctx.control.state = data.control_state_id;
@@ -94,6 +133,8 @@ $.fn.setStatusUpdater = function () {
 $.fn.startExperiment = function (group, experiment, force, result) {
  $(group).attr('disabled', 'disabled');
  
+ var can_number = ($(this).attr("name") === "start_strain") ? 0 : 1;
  if (!experiment || !experiment.match(/^[a-zA-Z0-9_-]+$/)) {
    result.text("Experiment names must be composed of alphanumeric characters" + 
                " or the characters -_-").addClass("fail");
@@ -106,16 +147,44 @@ $.fn.startExperiment = function (group, experiment, force, result) {
    data.force = 1;
  }
  
+ //Start the experiment
  $.ajax({
    url : mctx.control.api,
    data : data
  }).done(function (data) {
    if (!result.checkStatus(data)) {
-     return;
+    $(group).removeAttr('disabled');
+    return;
    }
-   result.html("&nbsp;");
-   toggleControls(true);
- }).always(function () {
+   
+   //Select the can
+   $.ajax({
+    url : mctx.api + "actuators",
+    data : {name : "can_select", set : can_number}
+   }).done(function (data) {
+    if (!result.checkStatus(data)) {
+     $(group).removeAttr('disabled');
+     return;
+    }
+    
+    //Enable the can
+    $.ajax({
+      url : mctx.api + 'actuators',
+      data : {name : "can_enable", set : 1}
+    }).done(function (data) {
+      if (!result.checkStatus(data)) {
+        $(group).removeAttr('disabled');
+        return;
+      }
+      result.html("&nbsp;");
+      toggleControls(true);
+    }).fail(function () {
+      $(group).removeAttr('disabled');
+    });
+   }).fail(function () {
+    $(group).removeAttr('disabled');
+   });
+ }).fail(function () {
    $(group).removeAttr('disabled');
  });
 };
@@ -151,11 +220,11 @@ $.fn.setPressure = function(pressure, result) {
     pressure[k] = n;
   }
   
-  var set = pressure['set'] + "," + pressure['wait'] + ","
-            pressure['size'] + "," + pressure['count'];
-  $.ajax({
+  var set = pressure['set'] + "_" + pressure['wait'] + "_" +
+            pressure['step'] + "_" + pressure['count'];
+  return $.ajax({
     url : mctx.api + "actuators",
-    data : {id : mctx.actuator.pressure_regulator, set : set}
+    data : {name : "pregulator", set : set}
   }).done(function (data) {
     if (!result.checkStatus(data)) {
       return;
index 22d14d7..851fff2 100644 (file)
@@ -11,6 +11,8 @@ mctx.graph.api.sensors = mctx.api + "sensors";
 mctx.graph.api.actuators = mctx.api + "actuators";
 mctx.sensors = {};
 mctx.actuators = {};
+
+mctx.graph.devices = {};
 mctx.graph.dependent = null;
 mctx.graph.independent = null;
 mctx.graph.timer = null;
@@ -64,13 +66,17 @@ $.fn.deployDevices = function(input_type, check_first, group) {
   var container = this;
   var apply = function(dict, prefix) {
     $.each(dict, function(key, val) {
+      //Unique id (name mangling)
+      var id = container.attr('id') + "_" + prefix + "_" + val.name;
+      
       var attributes = {
-          'type' : input_type, 'value' : key, 'alt' : val,
+          'type' : input_type, 'value' : key, 'alt' : val.name,
           'class' : prefix, 'name' : group, 
-          'id' : prefix + '_' + val //Unique id (name mangling)
+          'id' : id
       };
+      
       var entry = $("<input/>", attributes);
-      var label = $("<label/>", {'for' : prefix + '_' + val, 'text' : val}); 
+      var label = $("<label/>", {'for' : id, 'text' : val.name}); 
       entry.prop("checked", check_first);
       check_first = false;
       container.append(entry).append(label);
@@ -178,16 +184,20 @@ function graphUpdater() {
         var plot_data = [];
         
         yaxis.each(function() {
+          var series = {};
+          series.label = $(this).attr("alt");
+          
           //alert("Add " + $(this).val() + " to plot");
           if (xaxis.attr("alt") === "time") {
             //alert("Against time");
-            plot_data.push(devices[$(this).attr("alt")].data);
+            series.data = devices[$(this).attr("alt")].data;
           } else {
             var result = []
             dataMerge(devices[xaxis.attr("alt")].data, 
                       devices[$(this).attr("alt")].data, result);
-            plot_data.push(result);
+            series.data = result;
           }
+          plot_data.push(series);
         });
         
         if (mctx.graph.chart !== null) {
@@ -195,7 +205,12 @@ function graphUpdater() {
           mctx.graph.chart.setupGrid(); 
           mctx.graph.chart.draw();
         } else {
-          mctx.graph.chart = $.plot("#graph", plot_data);
+          var options = {
+            legend : {
+              container : "#graph-legend"
+            }
+          };
+          mctx.graph.chart = $.plot("#graph", plot_data, options);
         }
         mctx.graph.timer = setTimeout(updater, 1000);
       }
index 3df2bac..1007510 100644 (file)
@@ -123,8 +123,26 @@ function runBeforeLoad(isLoginPage) {
                 window.location = mctx.location + "login.html";
             }
         } else {
-            mctx.friendlyName = data.friendly_name;
+            mctx.friendlyName = data.user_name;
         }
+        
+        $(document).ready(function () {
+          //Show the content!
+          $("#content").css("display", "block");
+          
+          //Set the welcome bar
+          var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
+          $("#welcome-container").text("Welcome"+ name + "!");
+          $("#logout-container").css("display", "block");
+          //$("#menu-container").populateNavbar();
+
+          $("#logout").click(function () {
+            $("#logout").logout();
+          });
+          
+          //Enable the error log, if present
+          $("#errorlog").setErrorLog();
+        });
     }).fail(function (jqHXR) {
         if (mctx.debug) {
             debugLog("Failed to ident server. Is API running?")
@@ -152,74 +170,6 @@ $.fn.populateNavMenu = function() {
     return this;
 }
 
-/**
-* Sets the camera autoupdater
-* Obsolete?
-* @returns {$.fn}
-*/
-$.fn.setCamera = function () {
-    var url = mctx.api + "image";  //http://beaglebone/api/image
-    var update = true;
-
-    //Stop updating if we can't retrieve an image!
-    this.error(function() {
-        update = false;
-    });
-
-    var parent = this;
-
-    var updater = function() {
-        if (!update) {
-            alert("Cam fail");
-            parent.attr("src", "");
-            return;
-        }
-        
-        parent.attr("src", url + "#" + (new Date()).getTime());
-        
-        setTimeout(updater, 10000);
-    };
-
-    updater();
-    return this;
-};
-
-/**
-* Sets the strain graphs to graph stuff. Obsolete?
-* @returns {$.fn}
-*/
-$.fn.setStrainGraphs = function () {
-    var sensor_url = mctx.api + "sensors";
-    var graphdiv = this;
-
-    var updater = function () {
-        var time_limit = mctx.strain_gauges.time_limit;
-        var responses = new Array(mctx.strain_gauges.ids.length);
-        
-        for (var i = 0; i < mctx.strain_gauges.ids.length; i++) {
-            var parameters = {id : i, start_time: -time_limit};
-            responses[i] = $.ajax({url : sensor_url, data : parameters});
-        }
-        
-        $.when.apply(this, responses).then(function () {
-            var data = new Array(arguments.length);
-            for (var i = 0; i < arguments.length; i++) {
-                var raw_data = arguments[i][0].data;
-                var pruned_data = [];
-                var step = ~~(raw_data.length/100);
-                for (var j = 0; j < raw_data.length; j += step)
-                pruned_data.push(raw_data[j]); 
-                data[i] = pruned_data;
-            }
-            $.plot(graphdiv, data);
-            setTimeout(updater, 1000);
-        }, function () {debugLog("It crashed");});
-    };
-
-    updater();
-    return this;
-};
-
 /**
 * Performs a login attempt.
 * @returns The AJAX object of the login request */
@@ -308,39 +258,25 @@ $.fn.checkStatus = function(data) {
     $(this).text(data.description).removeClass("pass").addClass("fail");
     return false;
   }
+  $(this).removeClass("fail");
   return true;
 };
 
 $(document).ready(function () {
-  //Show the content!
-  $("#content").css("display", "block");
-  
-  //Set the welcome bar
-  var name = " " + (mctx.friendlyName ? mctx.friendlyName : "");
-  $("#welcome-container").text("Welcome"+ name + "!");
-  $("#logout-container").css("display", "block");
-  //$("#menu-container").populateNavbar();
-
-  $("#logout").click(function () {
-    $("#logout").logout();
-  });
-  
-  //Enable the error log, if present
-  $("#errorlog").setErrorLog();
-  
   //Enable the hide/show clicks
   $("#sidebar-hide").click(function () {
-    $("#sidebar").css("display", "none");
-    $("#sidebar-show").css("display", "inherit");
+    $("#sidebar").hide();
+    $("#sidebar-show").show();
     return this;
   });
 
   $("#sidebar-show").click(function () {
-    $("#sidebar-show").css("display", "none");
-    $("#sidebar").css("display", "inherit");
+    $("#sidebar-show").hide();
+    $("#sidebar").show();
     return this;
   });
 });
+
 $(document).ajaxError(function (event, jqXHR) {
     //console.log("AJAX query failed with: " + jqXHR.status + " (" + jqXHR.statusText + ")");
 });
\ No newline at end of file
index f282aba..9e0c551 100644 (file)
@@ -388,6 +388,17 @@ form.controls {
        font-size:20px;
 }
 
+/** For links on the data page **/
+.datalink {
+       color: black;
+       text-decoration:none;
+}
+
+.datalink:hover {
+       color: blue;
+       text-decoration:underline;
+}
+
 /** Hack **/
 .clear {
   clear: both;
diff --git a/testing/MCTXWeb/public_html/stream.html b/testing/MCTXWeb/public_html/stream.html
new file mode 100644 (file)
index 0000000..69b08dc
--- /dev/null
@@ -0,0 +1,5 @@
+<head>
+<meta http-equiv="refresh" content="0">
+</head>
+
+<IMG SRC="/api/image">
diff --git a/testing/MCTXWeb/public_html/values.html b/testing/MCTXWeb/public_html/values.html
new file mode 100644 (file)
index 0000000..995aa06
--- /dev/null
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+  <head>
+    <title>MCTX3420 Web Interface</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <!--[if lte IE 8]>
+      <script language="javascript" type="text/javascript" src="static/excanvas.min.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="static/jquery-1.10.1.min.js"></script>
+    <script type="text/javascript" src="static/jquery.flot.min.js"></script>
+    <script type="text/javascript" src="static/mctx.gui.js"></script>
+    <script type="text/javascript" src="static/mctx.control.js"></script>
+    
+    <link rel="stylesheet" type="text/css" href="static/style.css">
+    <link rel="stylesheet" type="text/css" href="static/nav-menu.css">
+    <script type="text/javascript">
+      function initialiseTable(container, prefix, data) {
+        var hrow = $("<tr/>");
+        var vrow = $("<tr/>");
+        var c = 0;
+        
+        for (var key in data) {
+          var value = data[key];
+          var id = prefix + value.name;
+          var h = $("<td/>").append($("<label/>", {for : id, text : value.name}));
+          var e = $("<td/>").append($("<input/>", {id : id, type : "text", readonly : true, value : value.value[1]}));
+          hrow.append(h);
+          vrow.append(e);
+          
+          if (++c >= 2) {
+            container.append(hrow).append(vrow);
+            c = 0;
+            hrow = $("<tr/>");
+            vrow = $("<tr/>");
+          }
+        }
+        if (c > 0) {
+          container.append(hrow).append(vrow);
+        }
+      }
+      
+      function updateTable(prefix, data) {
+        for (var key in data) {
+          var value = data[key];
+          var id = prefix + value.name;
+          $("#" + id).val(value.value[1]);
+        }
+      }
+    
+      $.fn.setValueUpdater = function (data) {
+        initialiseTable($("#sensor-values table"), "sensors_", data.sensors);
+        initialiseTable($("#actuator-values table"), "actuators_", data.actuators);
+        
+        var updater = function () {
+          $.ajax({
+            url : mctx.api + 'identify',
+            data : {'sensors' : 1, 'actuators' : 1}
+          }).done(function (data) {
+            updateTable("sensors_", data.sensors);
+            updateTable("actuators_", data.actuators);
+            setTimeout(updater, 5000);
+          }).fail(function () {
+            $("#error-message").text("Connection failure").addClass("fail");
+          });
+        };
+        
+        setTimeout(updater, 5000);
+      };
+      
+      runBeforeLoad().done(function () {
+        $(document).ready(function () {
+          $.ajax({
+            url : mctx.api + 'identify',
+            data : {'sensors' : 1, 'actuators' : 1}
+          }).done(function (data) {
+            if (data.control_state !== "Running") {
+              $("#error-message").text("Experiment not running").addClass("fail");
+            } else {
+              $("#values-widget").setValueUpdater(data);
+            }
+          }).fail(function () {
+            $("#error-message").text("Connection failure").addClass("fail");
+          });
+       });       
+      }).fail(function () {
+        $(document).ready(function () {
+         $("#error-message").text("Connection failure").addClass("fail");
+       });  
+      });
+    </script>
+  </head>
+  
+  <body>
+    <div id="header-wrap">
+      <div id="header">
+        <div id="leftnav">
+          <a href="http://www.uwa.edu.au/" target="_blank">
+            <img alt = "The University of Western Australia"
+            src="static/uwacrest-text.png">
+          </a>
+          <span id="title">Exploding Cans</span>
+        </div>
+        <div id="rightnav">
+          <span id="welcome-container">
+          </span>
+          <span id="date">
+            <script type="text/javascript">getDate();</script>
+          </span>
+          <div id="logout-container">
+            <form action="#">
+              <div>
+                <input type="button" id="logout" value="Logout">
+              </div>
+            </form>
+          </div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>
+    <!-- End header -->
+    
+    <div id="content-wrap">
+      <noscript>
+        <div class="widget centre">
+          <div class="title">JavaScript required</div>
+          This website requires JavaScript to function correctly.
+          Please enable JavaScript to use this site.
+        </div>
+      </noscript>
+
+      <div id="content">
+        <div class="widget" id="sidebar-show">&gt;</div>
+        <div id="sidebar">
+          <div class="widget">
+            <div id="sidebar-hide">&lt;</div>
+            <div class="title">Navigation menu</div>
+            <div id="sidebar-menu" class="nav-menu">
+              <ul>
+                <li><a href="index.html"><span>Home</span></a></li>
+                <li><a href="control.html"><span>Experiment control</span></a></li>
+                <li><a href="graph.html"><span>Experiment graphs</span></a></li>
+                <li><a href="values.html"><span>Experiment data (live)</span></a></li>
+                <li><a href="data.html"><span>Experiment data</span></a></li>
+                <li><a href="pintest.html"><span>Pin debugging</span></a></li>
+                <li class="last"><a href="help.html"><span>Help</span></a></li>
+              </ul>
+            </div>
+          </div>
+        </div>
+        <!-- End sidebar -->
+
+        <div id="main">
+          <div id="values-widget" class="widget">
+            <p class="right" id="error-message">
+              &nbsp;
+            </p>
+            <div class="sub-title">Sensors</div>
+            <form id="sensor-values" action="#" class="nice clear">
+              <table>
+              </table>
+            </form>
+            <div class="sub-title">Actuators</div>
+            <form id="actuator-values" action="#" class="nice clear">
+              <table>
+              </table>
+            </form>
+          </div>
+        </div>
+        <!-- End main content -->
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/testing/fastcgi-approach/post/post.c b/testing/fastcgi-approach/post/post.c
new file mode 100644 (file)
index 0000000..7cd803d
--- /dev/null
@@ -0,0 +1,53 @@
+#include <fcgi_stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+char *FCGI_URLDecode(char *buf);
+
+int main() {
+       while (FCGI_Accept() >= 0) {
+               char buf[BUFSIZ];
+               printf("Content-type: text/plain\r\n\r\n");
+
+
+               while(fgets(buf, BUFSIZ, stdin)) {
+                       printf("POST (raw):\r\n");
+                       printf("%s", buf);
+                       printf("\r\nPOST (decoded):\r\n");
+                       printf("%s", FCGI_URLDecode(buf));
+               }
+
+               snprintf(buf, BUFSIZ, "%s", getenv("QUERY_STRING"));
+               printf("\r\nGET (raw):\r\n");
+               printf("%s", getenv("QUERY_STRING"));
+
+               printf("\r\nGET (decoded):\r\n");
+               printf("%s", FCGI_URLDecode(buf));
+       }
+       return 0;
+
+}
+
+char *FCGI_URLDecode(char *buf) {
+       char *head = buf, *tail = buf;
+       char hex[3] = {0};
+       while (*tail) {
+               if (*tail == '%') {
+                       tail++;
+                       if (isxdigit(*tail) && isxdigit(*(tail+1))) {
+                               hex[0] = *tail++;
+                               hex[1] = *tail++;
+                               *head++ = (char)strtol(hex, NULL, 16);
+                       } else {
+                               head++;
+                       }
+               } else if (*tail == '+') {
+                       tail++;
+                       *head++ = ' ';
+               } else {
+                       *head++ = *tail++;
+               }
+       }
+       *head = 0;
+       return buf;
+}
index a27e003..c4863bd 100755 (executable)
@@ -13,7 +13,7 @@ import datetime
 import time
 
 #TODO: Replace with URL of testing server
-api_url = "https://daedalus/api"
+api_url = "https://192.168.1.10/api"
 
 def log(message):
        sys.stderr.write("%s: %s : %s\n" % (sys.argv[0], str(datetime.datetime.now()), message))

UCC git Repository :: git.ucc.asn.au