Merge branch 'master' of github:szmoore/MCTX3420 into report
authorSam Moore <[email protected]>
Mon, 28 Oct 2013 07:48:35 +0000 (15:48 +0800)
committerSam Moore <[email protected]>
Mon, 28 Oct 2013 07:48:35 +0000 (15:48 +0800)
Conflicts:
server/fastcgi.c
server/parameters

Also... calibrated sensors

124 files changed:
irc/log
reports/final/references/refs.bib
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.c
server/control.h
server/data.c
server/data.h
server/dilatometer.c
server/fastcgi.c
server/fastcgi.h
server/filetest [new file with mode: 0644]
server/login.c
server/main.c
server/microscope.c [new file with mode: 0644]
server/microscope.h [new file with mode: 0644]
server/parameters
server/sensor.c
server/sensor.h
server/sensors/common.c [new file with mode: 0644]
server/sensors/microphone.c [new file with mode: 0644]
server/sensors/microphone.h [new file with mode: 0644]
server/sensors/pressure.c
server/sensors/pressure.h
server/sensors/strain.c
server/sensors/strain.h
testing/Camera/test 1/test_blurred.jpg [new file with mode: 0644]
testing/Camera/test 1/test_edge.jpg [new file with mode: 0644]
testing/Camera/test 1/test_edge_avg.jpg [new file with mode: 0644]
testing/Camera/test 1/testimage.jpg [new file with mode: 0644]
testing/Camera/test 1/values [new file with mode: 0644]
testing/Camera/test 1/values~ [new file with mode: 0644]
testing/Camera/test 2/test_blurred.jpg [new file with mode: 0644]
testing/Camera/test 2/test_edge.jpg [new file with mode: 0644]
testing/Camera/test 2/test_edge_avg.jpg [new file with mode: 0644]
testing/Camera/test 2/testimage2.jpg [new file with mode: 0644]
testing/Camera/test 2/values [new file with mode: 0644]
testing/Camera/test 3/test_blurred.jpg [new file with mode: 0644]
testing/Camera/test 3/test_edge.jpg [new file with mode: 0644]
testing/Camera/test 3/test_edge_avg.jpg [new file with mode: 0644]
testing/Camera/test 3/testimage2.jpg [new file with mode: 0644]
testing/Camera/test 3/values [new file with mode: 0644]
testing/Camera/test 3/values~ [new file with mode: 0644]
testing/Camera/test 4/test_blurred.jpg [new file with mode: 0644]
testing/Camera/test 4/test_edge.jpg [new file with mode: 0644]
testing/Camera/test 4/test_edge_avg.jpg [new file with mode: 0644]
testing/Camera/test 4/testimage2.jpg [new file with mode: 0644]
testing/Camera/test 4/values [new file with mode: 0644]
testing/Camera/test 4/values~ [new file with mode: 0644]
testing/Camera/test 5/test_blurred.jpg [new file with mode: 0644]
testing/Camera/test 5/test_edge.jpg [new file with mode: 0644]
testing/Camera/test 5/test_edge_avg.jpg [new file with mode: 0644]
testing/Camera/test 5/testimage.jpg [new file with mode: 0644]
testing/Camera/test 5/values [new file with mode: 0644]
testing/Camera/test 5/values~ [new file with mode: 0644]
testing/Camera/test_blurred.jpg [new file with mode: 0644]
testing/Camera/test_edge.jpg [new file with mode: 0644]
testing/Camera/test_edge_avg.jpg [new file with mode: 0644]
testing/Camera/testimage.jpg [new file with mode: 0644]
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/sbd4.png
testing/MCTXWeb/public_html/static/style.css
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

diff --git a/irc/log b/irc/log
index ae6a317..3faa7f5 100644 (file)
--- a/irc/log
+++ b/irc/log
 22:00 < jtanx> and you can hide the navigation bar
 22:00 < jtanx> for more space
 22:11 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Day changed Sat Oct 19 2013
+07:59 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+10:39 < jtanx> blegh, now to start on the js for the control page
+11:10 < jtanx> what would you say if instead of all this chdir business
+11:10 < jtanx> one of the initial options to start the software is to specify where you want experiments to be held
+11:11 < jtanx> e.g /usr/share/experiments or whatever
+11:11 < jtanx> then you build the path based on the username
+11:25 < sam_moore> That sounds good
+11:26 < jtanx> Okay, I'll try to do that then
+14:37 < sam_moore> There's some kind of error in mctx.gui.js now
+14:37 < jtanx> what's happening?
+14:37 < sam_moore> A syntax error
+14:37 < jtanx> let me check
+14:38 < jtanx> what page is this on?
+14:38 < sam_moore> Line 272 (mctx.gui.js): outdiv[0].scrollHeight - outdiv.height()
+14:38 < sam_moore> Caused by loading graph.html
+14:38 < jtanx> ahhhh
+14:38 < jtanx> I know why
+14:38 < jtanx> maybe
+14:39 < jtanx> I decided to just to $("#errorlog").seterrorlog in mctx.gui.js
+14:39 < jtanx> so I don't have to repeat that on every page
+14:39 < sam_moore> The debug function also causes some error in chromium
+14:39 < jtanx> but if the page deosn't have an error log
+14:39 < jtanx> hahaha
+14:39 < jtanx> chromium
+14:39 < jtanx> what does it say
+14:39 < sam_moore> Didn't I tell you that something was causing iceweasel to segfault? I have no choice! :(
+14:40 < jtanx> :P
+14:40 < sam_moore> console.log.apply(this, arguments); causes an uncaught type error: illegal invocation
+14:40 < sam_moore> I shall replace it with "alert"
+14:40 < jtanx> hmm
+14:41 < jtanx> it's because chrome has a different implementation of console.log
+14:41 < jtanx> http://stackoverflow.com/questions/9521921/why-does-console-log-apply-throw-an-illegal-invocation-error
+14:41 < jtanx> for that bug
+14:41 < sam_moore> Right
+14:42 < sam_moore> Why is the call to outdiv.scrollTop on line 272 spread over 3 lines?
+14:42 < sam_moore> ... That's probably why it syntax errors
+14:42 < jtanx> nah
+14:42 < jtanx> it's legit
+14:43 < jtanx> the problem is there's no div there if you don't define #errorlog somewhere in your html
+14:43 < sam_moore> I thought the html was supposed to load any elements specific to that page
+14:44 < sam_moore> Which should avoid this sort of thing happening
+14:44 < jtanx> Yeah, except that we were repeating the same sort of load code
+14:44 < jtanx> in most of the pages
+14:44 < jtanx> I've just added a check in seterrorlog
+14:44 < jtanx> which should ignore if there's no such div anyway
+14:44 < sam_moore> ok
+14:44 < jtanx> that whole 'experiment dir' thing is ~.~
+14:44 < jtanx> I think it's mostly done
+14:45 < sam_moore> Ok
+14:45 < jtanx> so
+14:45 < jtanx> instead of having a file 'name.exp'
+14:45 < sam_moore> Yeah that was hacky
+14:45 < jtanx> it's a folder 'name.exp'
+14:45 < jtanx> all the stuff for one experiment gets stuck in that folder
+14:45 < sam_moore> That's slightly less hacky
+14:45 < jtanx> so you have something like
+14:45 < sam_moore> Makes sense
+14:46 < jtanx>  /experiments_folder/username/experiment_name.exp
+14:46 < sam_moore> Cool
+14:46 < sam_moore> If it ever is changed to use local user accounts you can just set experiments_folder to /home
+14:47 < jtanx> yeah
+14:47 < jtanx> experiments_folder is just an argument you pass to it when you start it
+14:47 < jtanx> -e experiment_folder
+14:49 < sam_moore> Found a syntax error in graph.html now
+14:49 < jtanx> just pushing stuff now
+14:49 < jtanx> but what's the errro?
+14:50 < sam_moore> There's an unterminated bracket on the runBeforeLoad.done
+14:50 < sam_moore> I think
+14:50 < jtanx> ah
+14:50 < sam_moore> So many brackets
+14:50 < jtanx> hehehe
+14:50 < jtanx> yeah you're probably right
+14:50 < jtanx> netbeans is giving angry red on the bracket
+14:51 < jtanx> do you want me to push the fix for that?
+14:51 < sam_moore> Well there are other issues with the page
+14:52 < sam_moore> No method "always" where it's being chained on the document.ready
+14:53 < jtanx> no wait
+14:53 < sam_moore> Wait what the hell is it meant to do there
+14:53 < jtanx> [14:52:32.186] ReferenceError: mctx is not defined @ http://localhost:8383/MCTXWeb/static/mctx.graph.js:8
+14:53 < jtanx> hmm
+14:53 < jtanx> I broke something 
+14:53 < sam_moore> That's not the problem I'm getting, but generally there are lots of wierd things here
+14:53 < jtanx> yeah
+14:53 < jtanx> sorry
+14:53 < jtanx> it was probably working before I pushed changes yesterday
+14:54 < sam_moore> You have runBeforeLoad.done() calling $(document).ready which does nothing, but is chained to .always which will then call $(document).ready which then loads $("#graph-controls").setDevices()
+14:55 < sam_moore> ... I think what I originally had was runBeforeLoad.always() calling $("#graph-controls").setDevices
+14:55 < sam_moore> Possibly inside a document.ready
+14:55 < sam_moore> But now mctx.gui.js is calling document.ready itself...
+14:55 < jtanx> waiit
+14:55 < jtanx> I think it's ok now
+14:56 < jtanx> it was just brackets
+14:56 < jtanx> I'll try it on my server first
+14:58 < jtanx> yeah, I see what you mean
+15:01 < jtanx> ahh
+15:01 < sam_moore> Well I fixed it enough to test stuff
+15:01 < jtanx> yeah
+15:02 < jtanx> you planning on modifying stuff?
+15:02 < sam_moore> I think it's just a matter of having runBeforeLoad().done() call $(document).ready() and then initialise stuff
+15:02 < jtanx> I'll just keep it in my repo for now
+15:03 < sam_moore> With runBeforeLoad.fail if you want to handle bad stuff
+15:03 < jtanx> I just made it
+15:03 < jtanx> runBeforeLoad().always(function() {
+15:03 < jtanx>         $(document).ready(function() {
+15:03 < jtanx>           $("#graph-controls").setDevices();
+15:03 < jtanx>         });
+15:03 < jtanx>       });
+15:03 < sam_moore> Yep, that's what I just did
+15:03 < sam_moore> You can commit it and I'll pull it
+15:03 < jtanx> okay
+15:03 < sam_moore> If you fix the console and the errorlog things as well
+15:03 < jtanx> yep
+15:03 < jtanx> pushed that too
+15:05 < sam_moore> Hmm, start_time = -1 is not actually giving the most recent second of data
+15:05 < sam_moore> That might be a server API problem
+15:05 < jtanx> hmmm
+15:05 < sam_moore> But it might also explain why things were shitting themselves so much with the fast sampling rate
+15:06 < jtanx> what is it giving?
+15:06 < sam_moore> Because the jQuery would be getting hundreds of thousands of points...
+15:07 < sam_moore> At the moment the server API is not giving any points and giving back a stupid value for start_time
+15:07 < sam_moore> I think I might have broken it when I changed all the clocks
+15:07 < jtanx> when did you change ti?
+15:07 < sam_moore> Just now
+15:07 < jtanx> ah
+15:07 < sam_moore> I'm hoping that my first assumption about the thousands of points was the case before I broke it as well :P
+15:08 < sam_moore> (ie: It was already broken before I broke it more)
+15:08 < jtanx> ahahahah
+15:08 < jtanx> I can try
+15:08 < jtanx> I still have the old version
+15:09 < jtanx> with start_time=-1 it returns nothing
+15:09 < jtanx> start_time in the response is constant
+15:11 < sam_moore> Yeah I get wierd stuff
+15:11 < sam_moore> start_time = 17889.590701
+15:12 < sam_moore> "current_time" : 18589.539255
+15:12 < jtanx> is that with clock_gettime?
+15:12 < sam_moore> "running_time" : 699.948554,
+15:12 < sam_moore> Yes
+15:13 < sam_moore> Oh herdurp
+15:13 < sam_moore> start_time in the JSON response is *not* the same as start_time the parameter to the sensor
+15:13 < jtanx> yeah
+15:13 < sam_moore> .... should probably change that
+15:13 < jtanx> start time of experiment
+15:14 < jtanx> experiment_start_time?
+15:14 < jtanx> long variable names...
+15:14 < sam_moore> I think it's actually the program start time
+15:14 < jtanx> nup
+15:14 < jtanx> experiment starttime
+15:14 < sam_moore> Alright
+15:14 < jtanx> oh
+15:14 < jtanx> actually you may be right
+15:14 < sam_moore> Haha
+15:14 < jtanx> I thought i changed it
+15:14 < jtanx> wway back when i did the control stuff
+15:15 < jtanx> initially
+15:15 < sam_moore> ControlData has a start_time variable
+15:15 < jtanx> yeah
+15:15 < sam_moore> But g_options has a start_time variable
+15:15 < sam_moore> -_-
+15:15 < jtanx> that's confusing
+15:15 < jtanx> hahaha
+15:15 < sam_moore> Yeah, what idiot designed this...
+15:15 < jtanx> ~.~`
+15:19 < sam_moore> Right, sensor values are being saved relative to Control_GetStartTime (experiment starting time)
+15:19 < sam_moore> That's fine
+15:19 < jtanx> yep
+15:19 < sam_moore> Sensor_Handler gets "current_time" from that as well
+15:19 < jtanx> Thread safety on that is dodgey
+15:19 < jtanx> I must say
+15:20 < jtanx> but it's probably fine
+15:20 < sam_moore> It's fine, clock_gettime is posix thread safe
+15:21 < sam_moore> Oh, do you mean if the experiment changes
+15:21 < jtanx> yeah
+15:21 < jtanx> the whole *Control_GetStartTime() looks dodgy too
+15:22 < sam_moore> Perhaps just a "double Control_CurrentTime()" would be better
+15:22 < sam_moore> But it works
+15:22 < jtanx> hmm
+15:24 < sam_moore> Anyway, I think start_time=-1 is not giving any data because the default sampling rate is 1s and therefore current_time - 1 is outside the data range most of the time
+15:25 < sam_moore> start_time=-3 gives a single point with the default sampling rate
+15:25 < sam_moore> Increasing the sampling rate then you appear to get the right points
+15:31 < sam_moore> I'm going to repeat my timestamp test with the current software under regular kernel and RT linux
+15:32 < sam_moore> I know we can't ever get RT linux but I want to see if it makes much of a difference
+15:46 < jtanx> okay
+15:50 < jtanx> finally... back to actually coding the control page
+22:59 -!- jtanx [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+--- Day changed Sun Oct 20 2013
+15:56 -!- Callum [[email protected]] has joined #mctxuwa_softdev
+15:59 < Callum> hey sam, got a link to the dropbox?
+16:06 < sam_moore> https://www.dropbox.com/sh/km90dmbdrgin3mg/OimGp0qopv
+16:06 < Callum> thanks
+16:07 < sam_moore> I think I might be able to hack together a user management system based on UserCake
+16:08 < sam_moore> Someone else will have to make it look pretty though
+17:25 < Callum> opencv is fucking retarded.
+17:25 < Callum> C++ has a bunch of different functions for each individual type of blur
+17:25 < Callum> C has one function but you pass it an ID for which type of blur you want to use..
+17:30 < sam_moore> OpenCV used to be in C, but they moved it to C++
+17:30 < sam_moore> But it still supports the C API
+17:30 < sam_moore> Through some wierd magic
+17:30 < sam_moore> At least, if you run our program in valgrind, it reports a lot of stuff happening with things like "std::string" and "cv::Mat"; ie: C++ namespaces
+17:32 < Callum> yea. its a bit of a bitch to code in C...all the documentation is for c++ pretty much
+17:32 < Callum> like right now im trying to find out how to simply set all elements to 0.. where in c++ its just Mat::Scalar(0)
+17:32 < sam_moore> If you really want you can probably compile in C++, but I'm not sure if it will cause issues elsewhere
+17:33 < Callum> il just keep going like this. just a couple of things atm i need to figure out. hopefully thats it
+17:33 < sam_moore> Cool
+17:34 < sam_moore> The server now supports 3 entirely different authentication methods :S
+17:34 < Callum> haha. a little excessive.
+17:34 < Callum> hopefully they'll be happy with atleast one of them..
+17:34 < sam_moore> If we modify UserCake we can run our own user management system
+17:35 < sam_moore> I need to change it so that instead of users registering themselves it's the admin that adds users manually though
+17:35 < sam_moore> Also we'd have to get email working
+17:57 < Callum> uuh. so using one of his test images for the microscope, running it through Canny gives a bunch of squiggly lines all over the place
+17:57 < Callum> its funny, the part which is actually the edge...has the most black
+17:57 < Callum> il try some of his other images and see what it does
+20:22 -!- Callum [[email protected]] has quit [EOF From client]
+20:50 -!- MctxBot_ [[email protected]] has joined #mctxuwa_softdev
+21:05 -!- MctxBot [[email protected]] has quit [Ping timeout]
+21:06 -!- Irssi: #mctxuwa_softdev: Total of 2 nicks [0 ops, 0 halfops, 0 voices, 2 normal]
+21:08 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+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
+--- Day changed Sat Oct 26 2013
+08:43 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+09:22 -!- justin_kruger [[email protected]] has joined #mctxuwa_softdev
+09:23 -!- justin_kruger [[email protected]] has quit [EOF From client]
+09:27 -!- MctxBot [[email protected]] has quit [Ping timeout]
+09:38 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+13:42 -!- MctxBot [[email protected]] has quit [Ping timeout]
+15:00 -!- Callum [[email protected]] has joined #mctxuwa_softdev
+15:10 < Callum> just finished the code to get the edge. atm it just returns the position on their object. was thinking we'd just use the timestamp and go this was the additional width at this time, additional width at time +1, so rate of change is 2*difference etc
+15:11 < Callum> also pushing 3 images to testing (actually i should go abck and push original too) but to show it blurred, after canny and with the found edge imposed over it (without w.e values i had set at the time..)
+15:15 < jtanx> Nice
+15:16 < Callum> ok merged
+15:17 < Callum> i might need some help merging this into the server code too
+15:18 < Callum> just to make sure its all good
+15:34 < jtanx> Sure
+15:34 < jtanx> I'm really busy right now though with this other crappy project that I have to do
+15:35 < jtanx> If not today, then maybe during tomorrow's meeting (if we're having a meeting then?)
+15:37 -!- Callum [[email protected]] has left #mctxuwa_softdev []
+15:38 -!- Callum [[email protected]] has joined #mctxuwa_softdev
+15:55 -!- Callum [[email protected]] has quit [Ping timeout]
+16:06 -!- Callum [[email protected]] has joined #mctxuwa_softdev
+16:07 < Callum> we were meeting tomorrow? also i have an assignment due monday i havent started. was going to try and work through that and work on another project i have due..as well as this
+16:07 < Callum> still need to start my writeup for this.
+16:40 < Callum> also, if the image is completely black, the read function should return false (havent yet tested this but it should be the case)
+16:53 < Callum> also looking at justins diagram, why isnt the microscope not included?
+16:54 < Callum> or is it just both under "camera"
+17:01 < jtanx> Yeah
+17:01 < jtanx> I think it's just under "camera"
+17:02 < Callum> ok.
+17:02 < jtanx> I dunno, I thought we should try to meet some time next week
+17:02 < jtanx> monday's our usual meeting time
+17:02 < Callum> you said tomorrow though didnt you?
+17:03 < jtanx> Ohh
+17:03 < jtanx> sorry
+17:03 < jtanx> my bad
+17:03 < jtanx> I meant monday
+17:03 < Callum> was going to say, my body clock so out of wack its sunday already?
+17:03 < Callum> yea. if i can get the assignment done tomorrow il be happy to spend most oif the day trying to finish some of this off
+17:03 < jtanx> Haha mine is
+17:03 < Callum> to be fair, i normally work saturdays
+17:03 < Callum> i took it off to do work.
+17:04 < jtanx> :/
+17:04 < Callum> so it should feel like a sunday
+17:04 < jtanx> I had this assignment where I thought I had completed it, but when I reread the specs I found out that I missed a large portion of it
+17:05 < Callum> ouch
+17:05 < Callum> what assignment was this for?
+17:05 < jtanx> cits3242
+17:05 < jtanx> some programming unit
+17:05 < Callum> figured by the cits. :p
+17:05 < jtanx> hehe
+17:06 < jtanx> yeah, if it wasn't for that i'd be solely working on this project right now
+17:07 < Callum> yea iv kind of been working on 3 or 4 things all at once. and not gotten far in anything.
+17:07 < jtanx> yeah, it's always crap when things are due at the same time
+17:08 < jtanx> but 3-4 things at once? that's harsh
+17:12 < Callum> umm. assignment due tomorrow i havent started. i have anoither group project but we only really have 2 members and wev barely done anything (due friday, meant to have 4/5 members)
+17:12 < Callum> physics assignment also due on friday
+17:12 < Callum> just trying to figure out how to get it all done
+17:13 < Callum> not going to be a fun week. then after this weeks over its major catchup in the 3 units i have exams for
+17:13 < Callum> oh and this tute adrian decided to give us...should probably look at that some time too
+17:16 < Callum> whats the model for the microscope? cant find anything in dropbox
+17:22 < Callum> they are  using the microscope only on the non-exploding one im assuming?
+17:23 < jtanx> Yeah
+17:23 < jtanx> the microscope
+17:23 < jtanx> it's a kaiser baas something
+17:23 < jtanx> KBA03030
+17:24 < Callum> thanks
+17:57 -!- Callum [[email protected]] has quit ["ChatZilla 0.9.90.1 [Firefox 24.0/20130910160258]"]
+22:04 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+23:31 -!- jtanx [[email protected]] has left #mctxuwa_softdev []
+--- Day changed Sun Oct 27 2013
+07:59 -!- jtanx [[email protected]] has joined #mctxuwa_softdev
+08:46 -!- MctxBot [[email protected]] has quit [Ping timeout]
+15:08 < sam_moore> Blergh, what are we going to put in the "Results" part of this report?
+15:09 < sam_moore> "We plugged it all in and it didn't explode" ?
+15:13 < sam_moore> Also it looks like gliffy only lets you export one svg on the trial account
+15:14 < sam_moore> And if the image isn't exactly A4 size, pdf is terrible
+15:14 < sam_moore> So the remainder of my figures will be png
+15:20 < sam_moore> Do we need to find references for all these things that we worked out and/or already knew ourselves...
+15:20 < jtanx> haha
+15:21 < sam_moore> I feel like referencing git commits
+15:21 < jtanx> Yeah I was going to do that
+15:21 < sam_moore> We should do that
+15:21 < jtanx> results
+15:21 < jtanx> it seems to work
+15:21 < sam_moore> ...
+15:21 < sam_moore> I put in a "Customer Satisfaction" section under results
+15:22 < jtanx> :P
+15:22 < sam_moore> It says "No matter what we did, the customer was not satisfied"
+15:22 < sam_moore> Next year they'll probably be told the beaglebone is stupid and start doing it all on an arduino
+15:22 < jtanx> mm probably
+15:22 < sam_moore> And then get told that it needs a user management system...
+15:23 < jtanx> hahahaha
+15:23 < sam_moore> I'm having trouble with what level of assumptions I should make about the reader's knowledge
+15:23 < sam_moore> Do they know what a thread is?
+15:24 < sam_moore> Do they know what "high level" and "low level" mean
+15:24 < jtanx> Thats... what I was having difficulty with too
+15:24 < sam_moore> Do they know what a kernel is?
+15:24 < jtanx> how far back do you have to explain terminology
+15:24 < sam_moore> Or context switching...
+15:24 < sam_moore> Argh
+15:24 < jtanx> write a treatise on multithreaded computing
+15:24 < sam_moore> :S
+15:24 < sam_moore> This is rather simple multithreaded stuff really
+15:26 < sam_moore> I think we need a "terminology" section
+15:27 < jtanx> THat would be good
+15:30 < jtanx> oh yeah
+15:30 < jtanx> we should be able to use commas again
+15:30 < jtanx> for that actuator step thing
+15:30 < jtanx> because it decodes the string first
+15:31 < jtanx> not sure if that will introduce any security issues though
+15:32 < sam_moore> Who cares, ship it
+15:33 < sam_moore> But I don't think so?
+15:33 < jtanx> :P
+15:33 < jtanx> maybe
+15:33 < jtanx> I dunno
+15:33 < sam_moore> sscanf can at most read to the end of the string
+15:34 < jtanx> since it decodes %hh to the hex value
+15:34 < jtanx> I mean if you give it a url encoded string
+15:34 < jtanx> with %01
+15:34 < jtanx> it would change that to the character 0x01
+15:34 < jtanx> which is like some control code
+15:34 < sam_moore> Hmm
+15:34 < jtanx> we could limit it so anything less than 0x20 is a space
+15:34 < sam_moore> Just do that then
+15:35 < jtanx> but it's probably important to url decode strings
+15:35 < jtanx> what if someone had a , in their password
+15:35 < jtanx> or weird characters
+15:35 < sam_moore> God dammit
+15:35 < sam_moore> call isprint() ?
+15:35 < jtanx> nah I mean
+15:36 < jtanx> it should be fine if we convert anything < 0x20 to a space
+15:36 < jtanx> but it's better that we're url decoding stuff
+15:36 < sam_moore> Ok
+15:36 < jtanx> because if we didn't, the password string would be like %2d instead of -
+15:36 < jtanx> for example
+15:36 < sam_moore> Yeah, that would suck
+15:37 < sam_moore> Do you think Rowan and/or James will write their parts of the report?
+15:37 < jtanx> Maybe, I dunno
+15:38 < jtanx> Rowan was trying to copy the wiki, which was written mostly by Justin?
+15:38 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+15:38 < sam_moore> Yeah, that's not really cool
+15:38 < jtanx> :/
+15:38 < sam_moore> I don't think there's much that can be just put into the report from the wiki anyway
+15:38 < sam_moore> It's too informal
+15:38 < sam_moore> And has a lot of "TODO: Fill this in" pages
+15:38 < jtanx> Yeah, they're counted separately anyway
+15:39 < jtanx> as in, there's a specific mark for wiki pages
+15:39 < sam_moore> Oh dear
+15:39 < jtanx> (or equivalent documentation afaik)
+15:39 < sam_moore> Every area there's a mark for is just another area they can deduct marks for not liking
+15:39 < jtanx> Yeah, I'm not liking it very much
+15:40 < sam_moore> Are we supposed to talk about the other subsystems or focus on our own?
+15:40 < jtanx> I'm hoping to focus on our own really
+15:40 < jtanx> but I guess there would be overlap
+15:48 < jtanx> did you see the microscope stuff that callum did
+16:00 < sam_moore> I glanced at it
+16:00 < sam_moore> It's in the main directory not sensors/
+16:01 < sam_moore> Does it work?
+16:01 < jtanx> well from the test pictures it looks quite good
+16:01 < jtanx> not sure how well it will work with the real thing
+16:02 < sam_moore> Hahaha
+16:02 < sam_moore> If it works with what we were given, good enough
+16:02 < jtanx> Yep
+16:03 < sam_moore> We seem to end up having to actually implement all of sensors things
+16:03 < sam_moore> I guess I will add the calibration in at some point before Tuesday
+16:04 < jtanx> :/
+16:04 < sam_moore> It shouldn't be that hard, I've done it before
+16:04 < sam_moore> Convert raw value to nearest two indexes in an array
+16:04 < sam_moore> Interpolate between those two indexes
+16:05 < sam_moore> I think for the Microphone there's 1024 points because he tested it with a 10 bit ADC somehow
+16:05 < sam_moore> For other things...
+16:05 < sam_moore> There are 10 points
+16:05 < jtanx> 1024 points
+16:05 < jtanx> wait what
+16:06 < sam_moore> 1 point per ADC value
+16:06 < sam_moore> As in, I was sent a lookup table of ADC value -> dB level
+16:06 < jtanx> oh right
+16:06 < jtanx> yeah
+16:06 < jtanx> he was testing it on an arduino
+16:06 < jtanx> was the arduino calibrated properly
+16:07 < sam_moore> Lalala not listening...
+16:07 < jtanx> :P
+16:07 < sam_moore> Oh, it loks like I only have 20 points anyway
+16:09 < sam_moore> This calibration does assume that electronics uses exactly the same voltage divider that sensors guy used
+16:09 < jtanx> it's probably close enough
+16:09 < sam_moore> Well maybe
+16:09 < sam_moore> Just trusting the data sheet is probably close enough
+16:10 < sam_moore> I'm not sure I see the point in calibrating a device if you don't actually use the final setup to calibrate it...
+16:10 < jtanx> Yeah, true
+16:22 < sam_moore> Is the use of '*' in pointer names a "standard"... I always thought it was... how you made a variable a pointer
+16:22 < jtanx> Oh yeah that
+16:31 < sam_moore> God dammit I have no motivation left to do anything
+16:32 < jtanx> urgh
+16:32 -!- jtanx changed the topic of #mctxuwa_softdev to: :(
+16:34 < sam_moore> Should I put your part of the report into tex?
+16:34 < jtanx> Yeah, I guess
+16:34 < sam_moore> I think if I put your part and Justin's part combined with my hopelessly incomplete part...
+16:34 < sam_moore> we will actually have too many pages
+16:34 < jtanx> :/
+16:34 < jtanx> was there a hard limit?
+16:35 < jtanx> I probably need to cut down my section, too much rambling
+16:36 < sam_moore> I think it's fine
+16:37 < jtanx> I dunno
+16:37 < jtanx> I have the feeling I'm not following the guidelines correctly on what I'm supposed to talk about
+16:37 < sam_moore> We can't win either way
+16:37 < jtanx> Yeah, like where am I meant to find the references for half the stuff I did
+16:38 < sam_moore> Surely James Trevelyn would have got a few papers out of his Telelabs thing?
+16:39 < sam_moore> I'll leave it for now
+16:39 < jtanx> Okay
+16:41 < sam_moore> My section will be great when I actually include all those figures I refer to...
+19:33 -!- MctxBot [[email protected]] has quit [Ping timeout]
+21:26 -!- MctxBot [[email protected]] has joined #mctxuwa_softdev
+23:24 -!- jtanx [[email protected]] has quit ["it has been segmented"]
index 60805d2..852b0a1 100644 (file)
@@ -20,4 +20,5 @@
        title = "MCTX3420 2013 Git Repository on GitHub",
        howpublished = "\url{https://github.com/szmoore/MCTX3420}"
 }
+
        
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..1cd62d3
--- /dev/null
@@ -0,0 +1,45 @@
+#include "pregulator.h"
+#include "../bbb_pin.h"
+
+
+#include "../data.h"
+#define PREGULATOR_PWM ECAP0
+#define PREGULATOR_PERIOD 500000
+//16666667
+
+/** PWM duty cycles raw **/
+static double pwm_raw[] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6};
+/** Calibrated pressure values match with pwm_raw **/
+static double preg_cal[] = {96, 190, 285, 380, 475, 569};
+
+/**
+ * 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)
+{
+       double anti_calibrated = Data_Calibrate(value, preg_cal, pwm_raw, sizeof(pwm_raw)/sizeof(double));
+       if (anti_calibrated < 0)
+               anti_calibrated = 0;
+       if (anti_calibrated > 1)
+               anti_calibrated = 1;
+       return PWM_Set(PREGULATOR_PWM, false, PREGULATOR_PERIOD, anti_calibrated*(PREGULATOR_PERIOD));
+}
+
+bool Pregulator_Sanity(int id, double value)
+{
+       return (value >= 0 && value < 570);
+}
+
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 6ebaba7..f81ea00 100644 (file)
@@ -197,7 +197,7 @@ void Control_Handler(FCGIContext *context, char *params) {
                                                "%s", name);
                }
 
-               FCGI_AcceptJSON(context, "Ok", NULL);
+               FCGI_AcceptJSON(context, "Ok");
        }
 }
 
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 edfd4c3..19828bc 100644 (file)
@@ -356,3 +356,51 @@ DataFormat Data_GetFormat(FCGIValue * fmt)
        }
        return JSON;
 }
+
+/**
+ * Binary search for index of a double in an array
+ * @param value - The value
+ * @param x - The array
+ * @param size - Sizeof the array
+ */
+int FindClosest(double value, double x[], int size)
+{
+       int upper = size-1;
+       int lower = 0;
+       int index = 0;
+       while (upper - lower > 1)
+       {
+               index = lower + ((upper - lower)/2);
+               double look = x[index];
+               if (look > value)
+                       upper = index;
+               else if (look < value)
+                       lower = index;
+               else
+                       return index;
+       }
+
+       if (x[index] > value && index > 0)
+               --index;
+       return index;
+
+}
+
+/**
+ * Get calibrated value by interpolation in array y
+ * @param value - Raw measured value
+ * @param x - x values (raw values) of the data
+ * @param y - calibrated values
+ * @param size - Number of values in the arrays
+ * @returns interpolated calibrated value
+ */
+double Data_Calibrate(double value, double x[], double y[], int size)
+{
+       int i = FindClosest(value, x, size);
+       if (i >= size-1)
+       {
+               i = size-2;     
+       }
+       double dist = (value - x[i])/(x[i+1] - x[i]);
+       return y[i] + dist*(y[i+1]-y[i]);
+}
index df16efe..d6234fd 100644 (file)
@@ -54,4 +54,6 @@ extern void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, Data
 extern DataFormat Data_GetFormat(FCGIValue * fmt); // Helper; convert human readable format string to DataFormat
 
 
+extern double Data_Callibrate(double value, double map[], int map_size);
+
 #endif //_DATAPOINT_H
index a22fd64..5cfca0a 100644 (file)
 // test positions
 static double test_left, test_right;
 
+// Canny Edge algorithm variables
+int edgeThresh = 1;
+int lowThreshold;
+int const max_lowThreshold = 100;
+int ratio = 3;
+int kernel_size = 3;
+
 /** Buffer for storing image data. Stored as a  **/
-static CvMat * g_data = NULL;
+static CvMat * g_srcRGB  = NULL; // Source Image
+static CvMat * g_srcGray = NULL; // Gray scale of source image
+static CvMat * g_edges          = NULL; // Detected Edges
+static CvMat * g_data    = NULL; // Image to mask edges onto
 
 
 /** Camera capture pointer **/
@@ -24,19 +34,7 @@ static CvCapture * g_capture = NULL;
 void Dilatometer_TestImage()
 {
        
-       CvMat *g_dataRGB;
-       g_dataRGB = cvCreateMat(480, 640, CV_8UC3);
-       //Make sure left and right positions are sane
-       if( test_left < 0)
-               test_left = 0;
-       if( test_right > 639)
-               test_right = 639;
-       if( test_left > test_right)
-       {
-               int tmp = test_right;
-               test_right = test_left;
-               test_left = tmp;
-       }
+       g_srcRGB = cvCreateMat(480, 640, CV_8UC3);
 
        for( int x = 0; x < 640; ++x)
        {
@@ -45,7 +43,7 @@ void Dilatometer_TestImage()
                        CvScalar s; 
                        for( int i = 0; i < 3; ++i)
                        {
-                               s.val[i]  =  220 + (rand() % 1000) * 1e-2 - (rand() % 1000) * 1e-2;
+                               s.val[i]  =  210 + (rand() % 1000) * 1e-0 - (rand() % 1000) * 1e-0;
                                // Produce an exponential decay around left edge
                                if( x < test_left)
                                        s.val[i] *= exp( (x - test_left) / 25);
@@ -57,7 +55,7 @@ void Dilatometer_TestImage()
                                else
                                        s.val[i] *= exp( (test_right - x) / 25);                                
                        }       
-                       cvSet2D(g_dataRGB,y,x,s);
+                       cvSet2D(g_srcRGB,y,x,s);
                //      if( s.val[0] > 200)
                //              printf("row: %d, col: %d, %f\n", y, x, s.val[0]); 
                }
@@ -65,9 +63,9 @@ void Dilatometer_TestImage()
        }
        if (g_data == NULL)
        {
-               g_data = cvCreateMat(g_dataRGB->rows,g_dataRGB->cols,CV_8UC1); //IPL_DEPTH_8U?
+               g_data = cvCreateMat(g_srcRGB->rows,g_srcRGB->cols,CV_8UC1); //IPL_DEPTH_8U?
        }
-       cvCvtColor(g_dataRGB,g_data,CV_RGB2GRAY);
+       cvCvtColor(g_srcRGB,g_data,CV_RGB2GRAY);
 }      
 
 /**
@@ -101,11 +99,47 @@ static void Dilatometer_GetImage()
        //Need to implement camera
 }
 
+void CannyThreshold()
+{
+       
+       if (g_data == NULL)
+       {
+               g_data = cvCreateMat(g_srcGray->rows,g_srcGray->cols,CV_8UC1);
+       }
+
+       if ( g_edges == NULL)
+       {
+               g_edges = cvCreateMat(g_srcGray->rows,g_srcGray->cols,CV_8UC1);
+       }
+       
+       //g_data = 0;
+       cvShowImage("display", g_srcGray);
+       cvWaitKey(0);   
+       // Reduce noise with a kernel 3x3. Input the grayscale source image, output to edges. (0's mean it's determined from kernel sizes)
+       cvSmooth( g_srcGray, g_edges, CV_GAUSSIAN, 9, 9 ,0 ,0 );
+       
+       cvShowImage("display", g_edges);
+       cvWaitKey(0);   
+
+       // Find the edges in the image
+       cvCanny( g_edges, g_edges, lowThreshold, lowThreshold*ratio, kernel_size );
+
+       cvShowImage("display", g_edges);
+       cvWaitKey(0);   
+       
+       // Mask the edges over G_data
+       //.copyTo( g_data, g_edges);
+}
+
 // Test algorithm
 static void Dilatometer_GetImageTest( )
 {      
-       //Test image
-       Dilatometer_TestImage();
+       //Generates Test image
+       //Dilatometer_TestImage();
+       
+       //Load Test image
+       g_srcGray = cvLoadImageM ("testimage.jpg",CV_LOAD_IMAGE_GRAYSCALE );
+       CannyThreshold();
 }
 
 
@@ -115,14 +149,14 @@ static void Dilatometer_GetImageTest( )
  * @returns the average width of the can
  */
 double Dilatometer_Read(int samples)
-{
+{      
        //Get the latest image
        //Dilatometer_GetImage();
 
        Dilatometer_GetImageTest();
        
-       int width = g_data->cols;
-       int height = g_data->rows;
+       int width = g_srcGray->cols;
+       int height = g_srcGray->rows;
        // If the number of samples is greater than the image height, sample every row
        if( samples > height)
        {
@@ -146,13 +180,13 @@ double Dilatometer_Read(int samples)
                sample_height = ceil(height * (i + 1) / samples) -1;
                //printf("sample height is %d\n", sample_height);
 
-               //CvScalar test = cvGet2D(g_data, 150,300);
+               //CvScalar test = cvGet2D(g_srcGray, 150,300);
                //printf("test is %f,%f,%f,%f\n", test.val[0], test.val[1], test.val[2], test.val[3]);
 
 
                for ( int col = 0; col < width; col++)
                {
-                       CvScalar value = cvGet2D(g_data, sample_height, col);
+                       CvScalar value = cvGet2D(g_srcGray, sample_height, col);
                        if( value.val[0]> THRES)
                        {
                                edges[pos] += (double) col;
@@ -191,21 +225,34 @@ int main(int argc, char ** argv)
        test_right = 500;
        Dilatometer_Init();
 
-       cvNamedWindow( "display", CV_WINDOW_AUTOSIZE);
-       cvShowImage("display", g_data);
-       cvWaitKey(0);   
+//     cvNamedWindow( "display", CV_WINDOW_AUTOSIZE);
+//     cvShowImage("display", g_data);
+//     cvWaitKey(0);   
        double width;
-       for( int i = 0; i < 20; ++i)
+       /*for( int i = 0; i < 20; ++i)
        {
                test_left  -= i * (rand() % 1000) * 1e-3;
                test_right += i * (rand() % 1000) * 1e-3;
+
+               //Make sure left and right positions are sane
+               if( test_left < 0)
+                       test_left = 0;
+               if( test_right > 639)
+                       test_right = 639;
+               if( test_left > test_right)
+               {
+                       int tmp = test_right;
+                       test_right = test_left;
+                       test_left = tmp;
+               }
+
                width = Dilatometer_Read(5);
                cvNamedWindow( "display", CV_WINDOW_AUTOSIZE);
-               cvShowImage("display", g_data);
+               cvShowImage("display", g_srcGray);
                cvWaitKey(0); 
                double expected = test_right - test_left;
                double perc = 100 * (expected - width) / expected;
                printf("%d: Left: %.4f.    Width: %.4f.\n  Right: %.4f. Expected: %.4f. Percentage: %.4f\n", i, test_left, width, test_right, expected, perc);
-       }
+       }*/
 }
 
index d7e057b..c246781 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"
  * @param context The context to work in
  * @param params User specified paramters: [actuators, sensors]
  */ 
-static void IdentifyHandler(FCGIContext *context, char *params) {
+static void IdentifyHandler(FCGIContext *context, char *params)
+{
        bool ident_sensors = false, ident_actuators = false;
-       char control_key[CONTROL_KEY_BUFSIZ];
-       bool has_control;
        int i;
 
-       snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING"));
-       has_control = FCGI_HasControl(context, control_key);
-       
        FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_BOOL_T},
                                         {"actuators", &ident_actuators, FCGI_BOOL_T}};
        if (!FCGI_ParseRequest(context, params, values, 2))
@@ -50,11 +47,12 @@ 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);
+       
+       bool has_control = FCGI_HasControl(context);
        FCGI_JSONBool("logged_in", has_control);
        FCGI_JSONPair("user_name", has_control ? context->user_name : "");
        
@@ -67,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}");
        }
@@ -78,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}");
        }
@@ -165,11 +167,12 @@ bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType use
  * @param key The control key to be validated.
  * @return TRUE if authorized, FALSE if not.
  */
-bool FCGI_HasControl(FCGIContext *context, const char *key) {
+bool FCGI_HasControl(FCGIContext *context)
+{
        time_t now = time(NULL);
        int result = (now - context->control_timestamp) <= CONTROL_TIMEOUT &&
-                       key != NULL && context->control_key[0] != '\0' &&
-                       !strcmp(context->control_key, key);
+                       context->control_key[0] != '\0' &&
+                       !strcmp(context->control_key, context->received_key);
        if (result) {
                context->control_timestamp = now; //Update the control_timestamp
        }
@@ -181,12 +184,50 @@ bool FCGI_HasControl(FCGIContext *context, const char *key) {
  * Revokes the current control key, if present.
  * @param context The context to work in
  */
-void FCGI_ReleaseControl(FCGIContext *context) {
+void FCGI_ReleaseControl(FCGIContext *context)
+{
        *(context->control_key) = 0;
        // Note: context->user_name should *not* be cleared
        return;
 }
 
+/**
+ * Gets the control cookie
+ * @param buffer A storage buffer of exactly CONTROL_KEY_BUFSIZ length to
+                 store the control key
+ */
+void FCGI_GetControlCookie(char buffer[CONTROL_KEY_BUFSIZ])
+{
+       const char *cookies = getenv("COOKIE_STRING");
+       const char *start = strstr(cookies, "mctxkey=");
+
+       *buffer = 0; //Clear the buffer
+       if (start != NULL) {
+               int i;
+               start += 8; //length of mctxkey=
+               for (i = 0; i < CONTROL_KEY_BUFSIZ; i++) {
+                       if (*start == 0 || *start == ';') {
+                               break;
+                       }
+                       buffer[i] = *start++;
+               }
+               buffer[i] = 0;
+       }
+}
+
+/**
+ * Sends the control key to the user as a cookie.
+ * @param context the context to work in
+ * @param set Whether to set or unset the control cookie
+ */
+void FCGI_SendControlCookie(FCGIContext *context, bool set) {
+       if (set) {
+               printf("Set-Cookie: mctxkey=%s\r\n", context->control_key);
+       } else {
+               printf("Set-Cookie: mctxkey=\r\n");
+       }
+}
+
 /**
  * Extracts a key/value pair from a request string.
  * Note that the input is modified by this function.
@@ -347,12 +388,9 @@ void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code)
  * @param description A short description.
  * @param cookie Optional. If given, the cookie field is set to that value.
  */
-void FCGI_AcceptJSON(FCGIContext *context, const char *description, const char *cookie)
+void FCGI_AcceptJSON(FCGIContext *context, const char *description)
 {
        printf("Content-type: application/json; charset=utf-8\r\n");
-       if (cookie) {
-               printf("Set-Cookie: %s\r\n", cookie);
-       }
        printf("\r\n{\r\n");
        printf("\t\"module\" : \"%s\"", context->current_module);
        FCGI_JSONLong("status", STATUS_OK);
@@ -500,6 +538,42 @@ 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 val, hex[3] = {0};
+
+       while (*tail) {
+               if (*tail == '%') { //%hh hex to char
+                       tail++;
+                       if (isxdigit(*tail) && isxdigit(*(tail+1))) {
+                               hex[0] = *tail++;
+                               hex[1] = *tail++;
+                               char val = (char)strtol(hex, NULL, 16);
+                               //Control codes --> Space character
+                               *head++ = (val < 0x20) ? 0x20 : val;
+                       } 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.
@@ -514,32 +588,19 @@ void * FCGI_RequestLoop (void *data)
        while (FCGI_Accept() >= 0) {
                
                ModuleHandler module_handler = NULL;
-               char module[BUFSIZ], params[BUFSIZ], control_key[CONTROL_KEY_BUFSIZ];
+               char module[BUFSIZ], params[BUFSIZ];
                
                //strncpy doesn't zero-truncate properly
                snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL"));
-               snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING"));
-
                
-               //char cookies[BUFSIZ];
-               //snprintf(cookies, BUFSIZ, "%s", getenv("COOKIE_STRING"));
-               //Log(LOGDEBUG, "ALL cookies %s", cookies); //mmmm
-
-               //Hack to get the nameless cookie only
-               // (works as long as browsers send the nameless cookie first...)
-               snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING"));
-               // Ignore any other cookies if the nameless cookie is empty
-               for (int i = 0; i < CONTROL_KEY_BUFSIZ; ++i)
-               {
-                       if (control_key[i] == ';')
-                       {
-                               control_key[i] = '\0';
-                               break;
-                       }
-               }
+               //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);
-               Log(LOGDEBUG, "Control key: %s", control_key);
+               Log(LOGDEBUG, "Control key: %s", context.received_key);
 
                
                //Remove trailing slashes (if present) from module query
@@ -572,26 +633,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 (!FCGI_HasControl(&context, control_key))
+               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 de02501..a417a43 100644 (file)
@@ -50,6 +50,8 @@ typedef struct
        time_t control_timestamp;
        /**A SHA-1 hash that is the control key, determining who is logged in**/
        char control_key[CONTROL_KEY_BUFSIZ]; 
+       /**The received control key for the current request**/
+       char received_key[CONTROL_KEY_BUFSIZ];
        /**The IPv4 address of the logged-in user**/
        char control_ip[16];
        /**Determines if the user is an admin or not**/
@@ -68,11 +70,13 @@ typedef void (*ModuleHandler) (FCGIContext *context, char *params);
 
 extern bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType user_type);
 extern void FCGI_ReleaseControl(FCGIContext *context);
-extern bool FCGI_HasControl(FCGIContext *context, const char *key);
+extern bool FCGI_HasControl(FCGIContext *context);
+extern void FCGI_GetControlCookie(char buffer[CONTROL_KEY_BUFSIZ]);
+extern void FCGI_SendControlCookie(FCGIContext *context, bool set);
 extern char *FCGI_KeyPair(char *in, const char **key, const char **value);
 extern bool FCGI_ParseRequest(FCGIContext *context, char *params, FCGIValue values[], size_t count);
 extern void FCGI_BeginJSON(FCGIContext *context, StatusCodes status_code);
-extern void FCGI_AcceptJSON(FCGIContext *context, const char *description, const char *cookie);
+extern void FCGI_AcceptJSON(FCGIContext *context, const char *description);
 extern void FCGI_JSONPair(const char *key, const char *value);
 extern void FCGI_JSONLong(const char *key, long value);
 extern void FCGI_JSONDouble(const char *key, double value);
@@ -81,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 30105cb..3445c04 100644 (file)
@@ -278,14 +278,18 @@ int Login_LDAP_Bind(const char * uri, const char * dn, const char * pass)
 void Logout_Handler(FCGIContext * context, char * params)
 {
        FCGI_ReleaseControl(context);
-       FCGI_AcceptJSON(context, "Logged out", "0");
+       FCGI_SendControlCookie(context, false); //Unset the cookie
+       FCGI_AcceptJSON(context, "Logged out");
 }
 
 
 /**
  * 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)
 {
@@ -327,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;
@@ -419,7 +423,8 @@ void Login_Handler(FCGIContext * context, char * params)
                {
                        FCGI_EscapeText(context->user_name); //Don't break javascript pls
                        // Give the user a cookie
-                       FCGI_AcceptJSON(context, "Logged in", context->control_key);
+                       FCGI_SendControlCookie(context, true); //Send the control key
+                       FCGI_AcceptJSON(context, "Logged in");
                        Log(LOGDEBUG, "Successful authentication for %s", user);
                }
                else
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();
diff --git a/server/microscope.c b/server/microscope.c
new file mode 100644 (file)
index 0000000..e388073
--- /dev/null
@@ -0,0 +1,246 @@
+/**
+ * @file microscope.c
+ * @purpose Implementation of microscope related functions
+ */
+
+#include "cv.h"
+#include "highgui_c.h"
+#include "microscope.h"
+#include <math.h>
+
+// test positions
+static double test_left, test_right;
+
+// Canny Edge algorithm variables
+int lowThreshold = 30;
+int ratio = 3;
+int kernel_size = 3;
+
+/** Buffer for storing image data. Stored as a  **/
+static CvMat * g_srcRGB  = NULL; // Source Image
+static CvMat * g_srcGray = NULL; // Gray scale of source image
+static CvMat * g_edges          = NULL; // Detected Edges
+static CvMat * g_data    = NULL; // Image to mask edges onto
+
+
+/** Camera capture pointer **/
+static CvCapture * g_capture = NULL;
+
+/**
+ * Create a test image using left as left edge and right as right edge positions
+ */
+void Dilatometer_TestImage()
+{
+       
+       g_srcRGB = cvCreateMat(480, 640, CV_8UC3);
+
+       for( int x = 0; x < 640; ++x)
+       {
+               for (int y = 0; y < 480; ++y)
+               {
+                       CvScalar s; 
+                       for( int i = 0; i < 3; ++i)
+                       {
+                               s.val[i]  =  210 + (rand() % 1000) * 1e-0 - (rand() % 1000) * 1e-0;
+                               // Produce an exponential decay around left edge
+                               if( x < test_left)
+                                       s.val[i] *= exp( (x - test_left) / 25);
+                               else if( x < 320)
+                                       s.val[i] *= exp( (test_left - x) / 25); 
+                               // Produce an exponential decay around right edge
+                               else if( x < test_right)
+                                       s.val[i] *= exp( (x - test_right) / 25); 
+                               else
+                                       s.val[i] *= exp( (test_right - x) / 25);                                
+                       }       
+                       cvSet2D(g_srcRGB,y,x,s);
+               //      if( s.val[0] > 200)
+               //              printf("row: %d, col: %d, %f\n", y, x, s.val[0]); 
+               }
+               
+       }
+       if (g_data == NULL)
+       {
+               g_data = cvCreateMat(g_srcRGB->rows,g_srcRGB->cols,CV_8UC1); //IPL_DEPTH_8U?
+       }
+       cvCvtColor(g_srcRGB,g_data,CV_RGB2GRAY);
+}      
+
+/**
+ * Initialise the dilatometer
+ */
+void Microscope_Init()
+{
+       
+       // Make an initial reading (will allocate memory the first time only).
+       double val;
+       Microscope_Read(&val, 1); 
+}
+
+/**
+ * Cleanup Interferometer stuff
+ */
+void Microscope_Cleanup()
+{
+       if (g_data != NULL)
+               cvReleaseMat(&g_data);
+
+       if (g_capture != NULL)
+               cvReleaseCapture(&g_capture);
+
+}
+
+/**
+ * Get an image from the Dilatometer
+ */
+static void Microscope_GetImage()
+{      
+       //Need to implement camera
+}
+
+void CannyThreshold()
+{
+       
+       if (g_data == NULL)
+       {
+               g_data = cvCreateMat(g_srcGray->rows,g_srcGray->cols,CV_8UC1);
+       }
+
+       if ( g_edges == NULL)
+       {
+               g_edges = cvCreateMat(g_srcGray->rows,g_srcGray->cols,CV_8UC1);
+       }
+       
+       //g_data = 0;
+       cvShowImage("display", g_srcGray);
+       cvWaitKey(0);   
+       // Reduce noise with a kernel 3x3. Input the grayscale source image, output to edges. (0's mean it's determined from kernel sizes)
+       cvSmooth( g_srcGray, g_edges, CV_GAUSSIAN, 9, 9 ,0 ,0 );
+       
+       cvShowImage("display", g_edges);
+       cvWaitKey(0);   
+       
+       // Find the edges in the image
+       lowThreshold = 35;
+       cvCanny( g_edges, g_edges, lowThreshold, lowThreshold*ratio, kernel_size );
+
+       cvShowImage("display", g_edges);
+       cvWaitKey(0);   
+       
+       // Mask the edges over G_data
+       //.copyTo( g_data, g_edges);
+}
+
+// Test algorithm
+static void Microscope_GetImageTest( )
+{      
+       //Generates Test image
+       //Dilatometer_TestImage();
+       
+       //Load Test image
+       g_srcGray = cvLoadImageM ("testimage.jpg",CV_LOAD_IMAGE_GRAYSCALE );
+       CannyThreshold();
+}
+
+
+ /**
+ * Read the microscope image. The value changed will correspond to the new location of the edge.
+ * @param val - Will store the read value if successful
+ * @param samples - Number of rows to scan (increasing will slow down performance!)
+ * @returns true on successful read
+ */
+bool Microscope_Read( double * value, int samples)
+{
+       bool result = false; 
+       double average = 0;
+       // Get the image from the camera
+       Microscope_GetImageTest();
+       
+       int width = g_edges->cols;
+       int height = g_edges->rows;
+       
+       // If the number of samples is greater than the image height, sample every row
+       if( samples > height)
+       {
+               samples = height;
+       }
+       
+       int sample_height;
+       int num_edges = 0;      // Number of edges. if each sample location has an edge, then num_edges = samples
+
+       for (int i=0; i<samples; i++)
+       {
+               // Determine the position in the rows to find the edges. 
+               // This will give you a number of evenly spaced samples
+               sample_height = ceil(height * (i + 1) / samples) -1;
+               
+               // Need to go through each pixel of a row and find all the locations of a line. If there is more than one pixel, average it. note this only works if the canny edge algorithm returns lines about the actual line (no outliers).
+               
+               int edge_location=0;
+               int num=0;
+               for ( int col = 0; col < width; col++)
+               {
+                       // Count the number of points
+                       // Get the threshold of the pixel at the current location
+                       CvScalar value = cvGet2D(g_edges, sample_height, col);
+                       //printf("row: %d, col: %d, value: %f\n",sample_height, col, value.val[0]);
+                       if( value.val[0]> THRES)
+                       {
+                               edge_location += col;
+                               num++;
+                       }
+               }
+               if( num > 0)
+               {
+                       average += ( edge_location / num );
+                       num_edges++;
+                       printf("average %f\n", average/num_edges);
+               }
+       }
+       if (num_edges > 0)
+               average /= num_edges;
+       
+       if( average > 0)
+       {       
+               result = true; //Successfully found an edge
+               *value = average;
+       }
+       return result;
+}
+
+// Overlays a line over the given edge position
+void Draw_Edge(double edge)
+{
+       CvScalar value;
+       value.val[0]=244;
+       for( int i = 0; i < g_srcGray->rows; i++)
+       {
+               cvSet2D(g_edges,i,edge,value);
+       }
+       cvShowImage("display", g_edges);
+       cvWaitKey(0);   
+}
+
+/**
+ * For testing purposes
+ */
+int main(int argc, char ** argv)
+{
+       //cvNamedWindow( "display", CV_WINDOW_AUTOSIZE );// Create a window for display.
+       //gettimeofday(&start, NULL);
+       test_left = 100;
+       test_right = 500;
+       Microscope_Init();
+       
+       cvNamedWindow( "display", CV_WINDOW_AUTOSIZE);
+//     cvShowImage("display", g_data);
+//     cvWaitKey(0);   
+       double width;
+       
+       double edge;
+       Microscope_Read(&edge,15);
+       //For testing purposes, overlay the given average line over the image
+       Draw_Edge(edge);
+
+}
+
diff --git a/server/microscope.h b/server/microscope.h
new file mode 100644 (file)
index 0000000..ca63b5e
--- /dev/null
@@ -0,0 +1,14 @@
+/**
+ * @file microscope.h
+ * @brief Declarations for functions to deal with microscope
+ */
+
+#include "common.h"
+
+//Threshold to determine the edge of the can
+#define THRES 230
+
+extern void Microscope_Init(); // Initialise the dilatometer
+extern void Microscope_Cleanup(); // Cleanup
+extern bool Microscope_Read( double * value, int samples); // Read the Microscope
+
index 5fdc595..26e2892 100644 (file)
@@ -17,16 +17,19 @@ LOGDEBUG=4
 verbosity="$LOGDEBUG"
 
 # Set to 1/0 to enable/disable the pin module (gives direct control over GPIO/ADC/PWM)
-#TODO: This option isn't actually implemented yet...
 pin_test="0"
 
 # Set to the URI to use authentication
-#auth_uri="ldap://192.168.1.1#ou=People,dc=daedalus"
-#auth_uri="ldaps://ldap.pheme.uwa.edu.au#ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" #UWA
+#auth_uri="ldap://192.168.1.1"
+#auth_uri="ldaps://ldap.pheme.uwa.edu.au" #UWA
 #auth_uri="/etc/shadow"
 #auth_uri="shadow"
-auth_uri="mysql://localhost#root,$(cat mysql_password)"
+
+# Set to the dn of the LDAP server
+ldap_base_dn="ou=People,dc=daedalus" # Testing
+#ldap_base_dn="ou=Users,ou=UWA,dc=uwads,dc=uwa,dc=edu,dc=au" #UWA
 
 
 ## OPTIONS TO BE PASSED TO SERVER; DO NOT EDIT
-parameters="-v $verbosity -p $pin_test -A $auth_uri"
+parameters="-v $verbosity -p $pin_test"
+# -A $auth_uri -d $ldap_base_dn"
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);
 
 
diff --git a/server/sensors/common.c b/server/sensors/common.c
new file mode 100644 (file)
index 0000000..cc17f0d
--- /dev/null
@@ -0,0 +1,3 @@
+#include "common.h"
+
+
diff --git a/server/sensors/microphone.c b/server/sensors/microphone.c
new file mode 100644 (file)
index 0000000..63a9d71
--- /dev/null
@@ -0,0 +1,40 @@
+/**
+ * @file microphone.c
+ * @purpose Implementation of Pressure reading functions
+ */
+
+#include "microphone.h"
+#include "../bbb_pin.h"
+#include "../log.h" // For Fatal()
+
+#define MIC_ADC ADC2
+
+double adc_raw[] = {524,668,733,991,1121,1264,1300,1437,1645,1789,1932,2033,2105,2148,2284,2528,3089};
+double mic_cal[] = {70,73,75,76.8,77.7,80,81.2,83.3,85.5,87.5,90.7,92.6,94.3,96.2,100,102,125};
+
+bool Microphone_Init(const char * name, int id)
+{
+       assert(sizeof(adc_raw) == sizeof(mic_cal));
+       return ADC_Export(MIC_ADC);
+}
+
+bool Microphone_Cleanup(int id)
+{
+       ADC_Unexport(MIC_ADC);
+       return true;
+}
+
+bool Microphone_Read(int id, double * value)
+{
+       int adc = 0;
+       if (!ADC_Read(MIC_ADC, &adc))
+               return false;
+       
+       *value = Data_Calibrate((double)adc, adc_raw, mic_cal, sizeof(adc_raw)/sizeof(double));
+       return true;
+}
+
+bool Microphone_Sanity(int id, double value)
+{
+       return true;
+}
diff --git a/server/sensors/microphone.h b/server/sensors/microphone.h
new file mode 100644 (file)
index 0000000..371506b
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef _MICROPHONE_H
+#define _MICROPHONE_H
+
+extern bool Microphone_Init(const char * name, int id);
+extern bool Microphone_Cleanup(int id);
+extern bool Microphone_Read(int id, double * value);
+extern bool Microphone_Sanity(int id, double value);
+
+#endif //_MICROPHONE_H
+
+
index 61cdbcf..699dda3 100644 (file)
@@ -6,9 +6,15 @@
 #include "pressure.h"
 #include "../bbb_pin.h"
 #include "../log.h" // For Fatal()
+#include "../data.h"
 
 #define PSI_TO_KPA 6.89475729
 
+/** Uncalibrated values in ADC readings **/
+static double high_raw[] = {642,910,1179,1445,1712,1980}; 
+/** Calibrated values in kPa **/
+static double high_cal[] = {95, 190, 285, 380, 474, 560};
+
 /**
  * Get the ADC number of a Pressure sensor
  * @param id - Id of the sensor
@@ -40,18 +46,26 @@ double Pressure_Callibrate(int id, int adc)
 {
        //double voltage = ADC_TO_VOLTS(adc); // convert reading to voltage
 
+
+
+       //TODO: Fix this
        switch (id)
        {
                case PRES_HIGH0:
                case PRES_HIGH1:
                {
+                       /*
                        static const double Vs = 5e3; // In mVs
                        static const double Pmin = 0.0 * PSI_TO_KPA;
                        static const double Pmax = 150.0 * PSI_TO_KPA;
                        double Vout = ADC_TO_MVOLTS(adc);
                        return ((Vout - 0.1*Vs)/(0.8*Vs))*(Pmax - Pmin) + Pmin;
+                       */
+
+                       return Data_Calibrate((double)adc, high_raw, high_cal, sizeof(high_raw)/sizeof(double));
                }       
                case PRES_LOW0:
+                       // Not calibrated!
                        return (200.0 * (adc / ADC_RAW_MAX));
                default:
                        Fatal("Unknown Pressure id %d", id);
@@ -102,3 +116,13 @@ bool Pressure_Read(int id, double * value)
        //pthread_mutex_unlock(&mutex);
        return result;
 }
+
+/**
+ * Sanity check the pressure reading
+ * @param value - The pressure reading (calibrated)
+ * @returns true iff the value is safe, false if it is dangerous
+ */
+bool Pressure_Sanity(int id, double value)
+{
+       return (value < 590);
+}
index 22d1adc..a37d918 100644 (file)
@@ -17,6 +17,7 @@ typedef enum
 extern bool Pressure_Init(const char * name, int id);
 extern bool Pressure_Cleanup(int id);
 extern bool Pressure_Read(int id, double * value);
+extern bool Pressure_Sanity(int id, double value);
 
 #endif //_PRESSURE_H
 
index 9492de7..7c78b56 100644 (file)
@@ -6,6 +6,8 @@
 #include <pthread.h>
 
 #define STRAIN_ADC ADC0
+// TODO: Choose this
+#define STRAIN_GPIO 45
 
 /**
  * Convert Strain gauge id number to a GPIO pin on the Mux
@@ -23,13 +25,13 @@ static int Strain_To_GPIO(StrainID id)
        switch (id)
        {
                case STRAIN0:
-                       return GPIO0_30;
+                       return 44;
                case STRAIN1:
-                       return GPIO1_28;
+                       return 26;
                case STRAIN2:
-                       return GPIO0_31;
+                       return 46;
                case STRAIN3:
-                       return GPIO1_16;
+                       return 65;
                default:
                        Fatal("Unknown StrainID %d", id);
                        return -1; // Should never happen
@@ -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/Camera/test 1/test_blurred.jpg b/testing/Camera/test 1/test_blurred.jpg
new file mode 100644 (file)
index 0000000..78c484d
Binary files /dev/null and b/testing/Camera/test 1/test_blurred.jpg differ
diff --git a/testing/Camera/test 1/test_edge.jpg b/testing/Camera/test 1/test_edge.jpg
new file mode 100644 (file)
index 0000000..9b076f8
Binary files /dev/null and b/testing/Camera/test 1/test_edge.jpg differ
diff --git a/testing/Camera/test 1/test_edge_avg.jpg b/testing/Camera/test 1/test_edge_avg.jpg
new file mode 100644 (file)
index 0000000..45ff0bd
Binary files /dev/null and b/testing/Camera/test 1/test_edge_avg.jpg differ
diff --git a/testing/Camera/test 1/testimage.jpg b/testing/Camera/test 1/testimage.jpg
new file mode 100644 (file)
index 0000000..caaf9e6
Binary files /dev/null and b/testing/Camera/test 1/testimage.jpg differ
diff --git a/testing/Camera/test 1/values b/testing/Camera/test 1/values
new file mode 100644 (file)
index 0000000..f7e99b7
--- /dev/null
@@ -0,0 +1,2 @@
+Blur = 9,9
+Low threshold = 35
diff --git a/testing/Camera/test 1/values~ b/testing/Camera/test 1/values~
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/testing/Camera/test 2/test_blurred.jpg b/testing/Camera/test 2/test_blurred.jpg
new file mode 100644 (file)
index 0000000..72ece1a
Binary files /dev/null and b/testing/Camera/test 2/test_blurred.jpg differ
diff --git a/testing/Camera/test 2/test_edge.jpg b/testing/Camera/test 2/test_edge.jpg
new file mode 100644 (file)
index 0000000..4e87ccb
Binary files /dev/null and b/testing/Camera/test 2/test_edge.jpg differ
diff --git a/testing/Camera/test 2/test_edge_avg.jpg b/testing/Camera/test 2/test_edge_avg.jpg
new file mode 100644 (file)
index 0000000..bc6248b
Binary files /dev/null and b/testing/Camera/test 2/test_edge_avg.jpg differ
diff --git a/testing/Camera/test 2/testimage2.jpg b/testing/Camera/test 2/testimage2.jpg
new file mode 100644 (file)
index 0000000..ef47265
Binary files /dev/null and b/testing/Camera/test 2/testimage2.jpg differ
diff --git a/testing/Camera/test 2/values b/testing/Camera/test 2/values
new file mode 100644 (file)
index 0000000..f7e99b7
--- /dev/null
@@ -0,0 +1,2 @@
+Blur = 9,9
+Low threshold = 35
diff --git a/testing/Camera/test 3/test_blurred.jpg b/testing/Camera/test 3/test_blurred.jpg
new file mode 100644 (file)
index 0000000..e76fa63
Binary files /dev/null and b/testing/Camera/test 3/test_blurred.jpg differ
diff --git a/testing/Camera/test 3/test_edge.jpg b/testing/Camera/test 3/test_edge.jpg
new file mode 100644 (file)
index 0000000..e35e374
Binary files /dev/null and b/testing/Camera/test 3/test_edge.jpg differ
diff --git a/testing/Camera/test 3/test_edge_avg.jpg b/testing/Camera/test 3/test_edge_avg.jpg
new file mode 100644 (file)
index 0000000..7d41059
Binary files /dev/null and b/testing/Camera/test 3/test_edge_avg.jpg differ
diff --git a/testing/Camera/test 3/testimage2.jpg b/testing/Camera/test 3/testimage2.jpg
new file mode 100644 (file)
index 0000000..ef47265
Binary files /dev/null and b/testing/Camera/test 3/testimage2.jpg differ
diff --git a/testing/Camera/test 3/values b/testing/Camera/test 3/values
new file mode 100644 (file)
index 0000000..0ed6414
--- /dev/null
@@ -0,0 +1,2 @@
+Blur = 5,5
+Low threshold = 40
diff --git a/testing/Camera/test 3/values~ b/testing/Camera/test 3/values~
new file mode 100644 (file)
index 0000000..f7e99b7
--- /dev/null
@@ -0,0 +1,2 @@
+Blur = 9,9
+Low threshold = 35
diff --git a/testing/Camera/test 4/test_blurred.jpg b/testing/Camera/test 4/test_blurred.jpg
new file mode 100644 (file)
index 0000000..73c4e62
Binary files /dev/null and b/testing/Camera/test 4/test_blurred.jpg differ
diff --git a/testing/Camera/test 4/test_edge.jpg b/testing/Camera/test 4/test_edge.jpg
new file mode 100644 (file)
index 0000000..323b365
Binary files /dev/null and b/testing/Camera/test 4/test_edge.jpg differ
diff --git a/testing/Camera/test 4/test_edge_avg.jpg b/testing/Camera/test 4/test_edge_avg.jpg
new file mode 100644 (file)
index 0000000..7849485
Binary files /dev/null and b/testing/Camera/test 4/test_edge_avg.jpg differ
diff --git a/testing/Camera/test 4/testimage2.jpg b/testing/Camera/test 4/testimage2.jpg
new file mode 100644 (file)
index 0000000..ef47265
Binary files /dev/null and b/testing/Camera/test 4/testimage2.jpg differ
diff --git a/testing/Camera/test 4/values b/testing/Camera/test 4/values
new file mode 100644 (file)
index 0000000..2ad1fb1
--- /dev/null
@@ -0,0 +1,2 @@
+Blur = 7,7
+Low threshold = 36
diff --git a/testing/Camera/test 4/values~ b/testing/Camera/test 4/values~
new file mode 100644 (file)
index 0000000..2ad1fb1
--- /dev/null
@@ -0,0 +1,2 @@
+Blur = 7,7
+Low threshold = 36
diff --git a/testing/Camera/test 5/test_blurred.jpg b/testing/Camera/test 5/test_blurred.jpg
new file mode 100644 (file)
index 0000000..e136542
Binary files /dev/null and b/testing/Camera/test 5/test_blurred.jpg differ
diff --git a/testing/Camera/test 5/test_edge.jpg b/testing/Camera/test 5/test_edge.jpg
new file mode 100644 (file)
index 0000000..2100541
Binary files /dev/null and b/testing/Camera/test 5/test_edge.jpg differ
diff --git a/testing/Camera/test 5/test_edge_avg.jpg b/testing/Camera/test 5/test_edge_avg.jpg
new file mode 100644 (file)
index 0000000..9cbc9a4
Binary files /dev/null and b/testing/Camera/test 5/test_edge_avg.jpg differ
diff --git a/testing/Camera/test 5/testimage.jpg b/testing/Camera/test 5/testimage.jpg
new file mode 100644 (file)
index 0000000..caaf9e6
Binary files /dev/null and b/testing/Camera/test 5/testimage.jpg differ
diff --git a/testing/Camera/test 5/values b/testing/Camera/test 5/values
new file mode 100644 (file)
index 0000000..0149619
--- /dev/null
@@ -0,0 +1,2 @@
+Blur = 5,5
+Low threshold = 35
diff --git a/testing/Camera/test 5/values~ b/testing/Camera/test 5/values~
new file mode 100644 (file)
index 0000000..2ad1fb1
--- /dev/null
@@ -0,0 +1,2 @@
+Blur = 7,7
+Low threshold = 36
diff --git a/testing/Camera/test_blurred.jpg b/testing/Camera/test_blurred.jpg
new file mode 100644 (file)
index 0000000..78c484d
Binary files /dev/null and b/testing/Camera/test_blurred.jpg differ
diff --git a/testing/Camera/test_edge.jpg b/testing/Camera/test_edge.jpg
new file mode 100644 (file)
index 0000000..9b076f8
Binary files /dev/null and b/testing/Camera/test_edge.jpg differ
diff --git a/testing/Camera/test_edge_avg.jpg b/testing/Camera/test_edge_avg.jpg
new file mode 100644 (file)
index 0000000..45ff0bd
Binary files /dev/null and b/testing/Camera/test_edge_avg.jpg differ
diff --git a/testing/Camera/testimage.jpg b/testing/Camera/testimage.jpg
new file mode 100644 (file)
index 0000000..caaf9e6
Binary files /dev/null and b/testing/Camera/testimage.jpg differ
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..960d945
--- /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="https://github.com/szmoore/MCTX3420/wiki/System-Overview#control" target="_blank" alt="Client PC" title="You">
+              <area shape="rect" coords="176,72,275,118" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-BeagleBone" target="_blank" alt="BBB">
+              <area shape="rect" coords="298,53,395,145" href="https://github.com/szmoore/MCTX3420/wiki/System-Overview#electronics" target="_blank" alt="Electronics">
+              <area shape="rect" coords="446,53,543,145" href="https://github.com/szmoore/MCTX3420/wiki/System-Overview#pneumatics" target="_blank" alt="Pneumatics">
+              <area shape="rect" coords="218,191,315,237" href="https://github.com/szmoore/MCTX3420/wiki/System-Overview#sensors" target="_blank" alt="Sensors">
+              <area shape="rect" coords="418,191,515,237" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors#camera" target="_blank" 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="https://github.com/szmoore/MCTX3420/wiki/System-Overview#case" target="_blank" alt="Case" id="case-btn">
+              <area shape="rect" coords="87,257,183,303" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Mounting" target="_blank" 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..6a1c4a2
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..94bd282 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>
             <div class="title">System diagram</div>
             <img id="sbd" src="static/sbd4.png" class="centre" 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="8,72,105,118" href="https://github.com/szmoore/MCTX3420/wiki/System-Overview#control" target="_blank" alt="Client PC" title="You">
+              <area shape="rect" coords="176,72,275,118" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-BeagleBone" target="_blank" alt="BBB">
+              <area shape="rect" coords="298,53,395,145" href="https://github.com/szmoore/MCTX3420/wiki/System-Overview#electronics" target="_blank" alt="Electronics">
+              <area shape="rect" coords="446,53,543,145" href="https://github.com/szmoore/MCTX3420/wiki/System-Overview#pneumatics" target="_blank" alt="Pneumatics">
+              <area shape="rect" coords="218,191,315,237" href="https://github.com/szmoore/MCTX3420/wiki/System-Overview#sensors" target="_blank" alt="Sensors">
+              <area shape="rect" coords="418,191,515,237" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Sensors#camera" target="_blank" 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="87,191,183,237" href="https://github.com/szmoore/MCTX3420/wiki/System-Overview#case" target="_blank" alt="Case" id="case-btn">
+              <area shape="rect" coords="87,257,183,303" href="https://github.com/szmoore/MCTX3420/wiki/Hardware:-Mounting" target="_blank" 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>
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 04a2688..6a1c4a2 100644 (file)
Binary files a/testing/MCTXWeb/public_html/static/sbd4.png and b/testing/MCTXWeb/public_html/static/sbd4.png differ
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/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