with the appropriate children
+ function Schematic(input) {
+ // set up diagram viewing parameters
+ this.grid = 8;
+ this.scale = 2;
+ this.origin_x = input.getAttribute("origin_x");
+ if (this.origin_x == undefined) this.origin_x = 0;
+ this.origin_y = input.getAttribute("origin_y");
+ if (this.origin_y == undefined) this.origin_y = 0;
+
+ this.window_list = []; // list of pop-up windows in increasing z order
+
+ // use user-supplied list of parts if supplied
+ // else just populate parts bin with all the parts
+ this.edits_allowed = true;
+ var parts = input.getAttribute('parts');
+ if (parts == undefined || parts == 'None') {
+ parts = new Array();
+ for (var p in parts_map) parts.push(p);
+ } else if (parts == '') {
+ this.edits_allowed = false;
+ parts = [];
+ } else parts = parts.split(',');
+
+ // now add the parts to the parts bin
+ this.parts_bin = [];
+ for (var i = 0; i < parts.length; i++) {
+ var part = new Part(this);
+ var pm = parts_map[parts[i]];
+ part.set_component(new pm[0](0,0,0),pm[1]);
+ this.parts_bin.push(part);
+ }
+
+ // use user-supplied list of analyses, otherwise provide them all
+ // analyses="" means no analyses
+ var analyses = input.getAttribute('analyses');
+ if (analyses == undefined || analyses == 'None')
+ analyses = ['dc','ac','tran'];
+ else if (analyses == '') analyses = [];
+ else analyses = analyses.split(',');
+
+ if (parts.length == 0 && analyses.length == 0) this.diagram_only = true;
+ else this.diagram_only = false;
+
+ // see what we need to submit. Expecting attribute of the form
+ // submit_analyses="{'tran':[[node_name,t1,t2,t3],...],
+ // 'ac':[[node_name,f1,f2,...],...]}"
+ var submit = input.getAttribute('submit_analyses');
+ if (submit && submit.indexOf('{') != -1)
+ this.submit_analyses = JSON.parse(submit);
+ else
+ this.submit_analyses = undefined;
+
+ // toolbar
+ this.tools = new Array();
+ this.toolbar = [];
+
+ if (!this.diagram_only) {
+ this.tools['help'] = this.add_tool(help_icon,'Help: display help page',this.help);
+ this.enable_tool('help',true);
+ this.toolbar.push(null); // spacer
+ }
+
+ if (this.edits_allowed) {
+ this.tools['zoomin'] = this.add_tool(zoomin_icon,'Zoom In: increase display magnification',this.zoomin);
+ this.enable_tool('zoomin',true);
+ this.tools['zoomout'] = this.add_tool(zoomout_icon,'Zoom Out: decrease display magnification',this.zoomout);
+ this.enable_tool('zoomout',true);
+ this.tools['zoomall'] = this.add_tool(zoomall_icon,'Zoom All: show entire diagram',this.zoomall);
+ this.enable_tool('zoomall',true);
+
+ this.tools['cut'] = this.add_tool(cut_icon,'Cut: move selected components from diagram to the clipboard',this.cut);
+ this.tools['copy'] = this.add_tool(copy_icon,'Copy: copy selected components into the clipboard',this.copy);
+ this.tools['paste'] = this.add_tool(paste_icon,'Paste: copy clipboard into the diagram',this.paste);
+ this.toolbar.push(null); // spacer
+ }
+
+ // simulation interface if cktsim.js is loaded
+ if (typeof cktsim != 'undefined') {
+ if (analyses.indexOf('dc') != -1) {
+ this.tools['dc'] = this.add_tool('DC','DC Analysis',this.dc_analysis);
+ this.enable_tool('dc',true);
+ this.dc_max_iters = '1000'; // default values dc solution
+ }
+
+ if (analyses.indexOf('ac') != -1) {
+ this.tools['ac'] = this.add_tool('AC','AC Small-Signal Analysis',this.setup_ac_analysis);
+ this.enable_tool('ac',true);
+ this.ac_npts = '50'; // default values for AC Analysis
+ this.ac_fstart = '10';
+ this.ac_fstop = '1G';
+ this.ac_source_name = undefined;
+ }
+
+ if (analyses.indexOf('tran') != -1) {
+ this.tools['tran'] = this.add_tool('TRAN','Transient Analysis',this.transient_analysis);
+ this.enable_tool('tran',true);
+ this.tran_npts = '100'; // default values for transient analysis
+ this.tran_tstop = '1';
+ }
+ }
+
+ // set up diagram canvas
+ this.canvas = document.createElement('canvas');
+ this.width = input.getAttribute('width');
+ this.width = parseInt(this.width == undefined ? '400' : this.width);
+ this.canvas.width = this.width;
+ this.height = input.getAttribute('height');
+ this.height = parseInt(this.height == undefined ? '300' : this.height);
+ this.canvas.height = this.height;
+
+ // repaint simply draws this buffer and then adds selected elements on top
+ this.bg_image = document.createElement('canvas');
+ this.bg_image.width = this.width;
+ this.bg_image.height = this.height;
+
+ if (!this.diagram_only) {
+ this.canvas.tabIndex = 1; // so we get keystrokes
+ this.canvas.style.borderStyle = 'solid';
+ this.canvas.style.borderWidth = '1px';
+ this.canvas.style.borderColor = grid_style;
+ this.canvas.style.outline = 'none';
+ }
+
+ this.canvas.schematic = this;
+ if (this.edits_allowed) {
+ this.canvas.addEventListener('mousemove',schematic_mouse_move,false);
+ this.canvas.addEventListener('mouseover',schematic_mouse_enter,false);
+ this.canvas.addEventListener('mouseout',schematic_mouse_leave,false);
+ this.canvas.addEventListener('mousedown',schematic_mouse_down,false);
+ this.canvas.addEventListener('mouseup',schematic_mouse_up,false);
+ this.canvas.addEventListener('dblclick',schematic_double_click,false);
+ this.canvas.addEventListener('keydown',schematic_key_down,false);
+ this.canvas.addEventListener('keyup',schematic_key_up,false);
+ }
+
+ // set up message area
+ if (!this.diagram_only) {
+ this.status_div = document.createElement('div');
+ this.status = document.createTextNode('');
+ this.status_div.appendChild(this.status);
+ this.status_div.style.height = status_height + 'px';
+ } else this.status_div = undefined;
+
+ this.connection_points = new Array(); // location string => list of cp's
+ this.components = [];
+
+ this.dragging = false;
+ this.drawCursor = false;
+ this.cursor_x = 0;
+ this.cursor_y = 0;
+ this.draw_cursor = undefined;
+ this.select_rect = undefined;
+ this.wire = undefined;
+
+ this.operating_point = undefined; // result from DC analysis
+ this.dc_results = undefined; // saved analysis results for submission
+ this.ac_results = undefined; // saved analysis results for submission
+ this.transient_results = undefined; // saved analysis results for submission
+
+ // state of modifier keys
+ this.ctrlKey = false;
+ this.shiftKey = false;
+ this.altKey = false;
+ this.cmdKey = false;
+
+ // make sure other code can find us!
+ input.schematic = this;
+ this.input = input;
+
+ // set up DOM -- use nested tables to do the layout
+ var table,tr,td;
+ table = document.createElement('table');
+ table.rules = 'none';
+ if (!this.diagram_only) {
+ table.frame = 'box';
+ table.style.borderStyle = 'solid';
+ table.style.borderWidth = '2px';
+ table.style.borderColor = normal_style;
+ table.style.backgroundColor = background_style;
+ }
+
+ // add tools to DOM
+ if (this.toolbar.length > 0) {
+ tr = document.createElement('tr');
+ table.appendChild(tr);
+ td = document.createElement('td');
+ td.style.verticalAlign = 'top';
+ td.colSpan = 2;
+ tr.appendChild(td);
+ for (var i = 0; i < this.toolbar.length; ++i) {
+ var tool = this.toolbar[i];
+ if (tool != null) td.appendChild(tool);
+ }
+ }
+
+ // add canvas and parts bin to DOM
+ tr = document.createElement('tr');
+ table.appendChild(tr);
+
+ td = document.createElement('td');
+ tr.appendChild(td);
+ var wrapper = document.createElement('div'); // for inserting pop-up windows
+ td.appendChild(wrapper);
+ wrapper.style.position = 'relative'; // so we can position subwindows
+ wrapper.appendChild(this.canvas);
+
+ td = document.createElement('td');
+ td.style.verticalAlign = 'top';
+ tr.appendChild(td);
+ var parts_table = document.createElement('table');
+ td.appendChild(parts_table);
+ parts_table.rules = 'none';
+ parts_table.frame = 'void';
+ parts_table.cellPadding = '0';
+ parts_table.cellSpacing = '0';
+
+ // fill in parts_table
+ var parts_per_column = Math.floor(this.height / (part_h + 5)); // mysterious extra padding
+ for (var i = 0; i < parts_per_column; ++i) {
+ tr = document.createElement('tr');
+ parts_table.appendChild(tr);
+ for (var j = i; j < this.parts_bin.length; j += parts_per_column) {
+ td = document.createElement('td');
+ tr.appendChild(td);
+ td.appendChild(this.parts_bin[j].canvas);
+ }
+ }
+
+ if (this.status_div != undefined) {
+ tr = document.createElement('tr');
+ table.appendChild(tr);
+ td = document.createElement('td');
+ tr.appendChild(td);
+ td.colSpan = 2;
+ td.appendChild(this.status_div);
+ }
+
+ // add to dom
+ // avoid Chrome bug that changes to text cursor whenever
+ // drag starts. Just do this in schematic tool...
+ var toplevel = document.createElement('div');
+ toplevel.onselectstart = function(){ return false; };
+ toplevel.appendChild(table);
+ this.input.parentNode.insertBefore(toplevel,this.input.nextSibling);
+
+ // process initial contents of diagram
+ this.load_schematic(this.input.getAttribute('value'),
+ this.input.getAttribute('initial_value'));
+ }
+
+ part_w = 42; // size of a parts bin compartment
+ part_h = 42;
+ status_height = 18;
+
+ Schematic.prototype.add_component = function(new_c) {
+ this.components.push(new_c);
+
+ // create undoable edit record here
+ }
+
+ Schematic.prototype.remove_component = function(c) {
+ var index = this.components.indexOf(c);
+ if (index != -1) this.components.splice(index,1);
+ }
+
+ Schematic.prototype.find_connections = function(cp) {
+ return this.connection_points[cp.location];
+ }
+
+ // add connection point to list of connection points at that location
+ Schematic.prototype.add_connection_point = function(cp) {
+ var cplist = this.connection_points[cp.location];
+ if (cplist) cplist.push(cp);
+ else {
+ cplist = [cp];
+ this.connection_points[cp.location] = cplist;
+ }
+
+ // return list of conincident connection points
+ return cplist;
+ }
+
+ // remove connection point from the list points at the old location
+ Schematic.prototype.remove_connection_point = function(cp,old_location) {
+ // remove cp from list at old location
+ var cplist = this.connection_points[old_location];
+ if (cplist) {
+ var index = cplist.indexOf(cp);
+ if (index != -1) {
+ cplist.splice(index,1);
+ // if no more connections at this location, remove
+ // entry from array to keep our search time short
+ if (cplist.length == 0)
+ delete this.connection_points[old_location];
+ }
+ }
+ }
+
+ // connection point has changed location: remove, then add
+ Schematic.prototype.update_connection_point = function(cp,old_location) {
+ this.remove_connection_point(cp,old_location);
+ return this.add_connection_point(cp);
+ }
+
+ // add a wire to the schematic
+ Schematic.prototype.add_wire = function(x1,y1,x2,y2) {
+ var new_wire = new Wire(x1,y1,x2,y2);
+ new_wire.add(this);
+ new_wire.move_end();
+ return new_wire;
+ }
+
+ Schematic.prototype.split_wire = function(w,cp) {
+ // remove bisected wire
+ w.remove();
+
+ // add two new wires with connection point cp in the middle
+ this.add_wire(w.x,w.y,cp.x,cp.y);
+ this.add_wire(w.x+w.dx,w.y+w.dy,cp.x,cp.y);
+ }
+
+ // see if connection points of component c split any wires
+ Schematic.prototype.check_wires = function(c) {
+ for (var i = 0; i < this.components.length; i++) {
+ var cc = this.components[i];
+ if (cc != c) { // don't check a component against itself
+ // only wires will return non-null from a bisect call
+ var cp = cc.bisect(c);
+ if (cp) {
+ // cc is a wire bisected by connection point cp
+ this.split_wire(cc,cp);
+ this.redraw_background();
+ }
+ }
+ }
+ }
+
+ // see if there are any existing connection points that bisect wire w
+ Schematic.prototype.check_connection_points = function(w) {
+ for (var locn in this.connection_points) {
+ var cplist = this.connection_points[locn];
+ if (cplist && w.bisect_cp(cplist[0])) {
+ this.split_wire(w,cplist[0]);
+ this.redraw_background();
+
+ // stop here, new wires introduced by split will do their own checks
+ return;
+ }
+ }
+ }
+
+ // merge collinear wires sharing an end point
+ Schematic.prototype.clean_up_wires = function() {
+ for (var locn in this.connection_points) {
+ var cplist = this.connection_points[locn];
+ if (cplist && cplist.length == 2) {
+ // found a connection with just two connections, see if they're wires
+ var c1 = cplist[0].parent;
+ var c2 = cplist[1].parent;
+ if (c1.type == 'w' && c2.type == 'w') {
+ var e1 = c1.other_end(cplist[0]);
+ var e2 = c2.other_end(cplist[1]);
+ var e3 = cplist[0]; // point shared by the two wires
+ if (collinear(e1,e2,e3)) {
+ c1.remove();
+ c2.remove();
+ this.add_wire(e1.x,e1.y,e2.x,e2.y);
+ }
+ }
+ }
+ }
+ }
+
+ Schematic.prototype.unselect_all = function(which) {
+ this.operating_point = undefined; // remove annotations
+
+ for (var i = this.components.length - 1; i >= 0; --i)
+ if (i != which) this.components[i].set_select(false);
+ }
+
+ Schematic.prototype.drag_begin = function() {
+ // let components know they're about to move
+ for (var i = this.components.length - 1; i >= 0; --i) {
+ var component = this.components[i];
+ if (component.selected) component.move_begin();
+ }
+
+ // remember where drag started
+ this.drag_x = this.cursor_x;
+ this.drag_y = this.cursor_y;
+ this.dragging = true;
+ }
+
+ Schematic.prototype.drag_end = function() {
+ // let components know they're done moving
+ for (var i = this.components.length - 1; i >= 0; --i) {
+ var component = this.components[i];
+ if (component.selected) component.move_end();
+ }
+ this.dragging = false;
+
+ this.clean_up_wires();
+ this.redraw_background();
+ }
+
+ Schematic.prototype.help = function() {
+ window.open('/static/handouts/schematic_tutorial.pdf');
+ }
+
+ // zoom diagram around current center point
+ Schematic.prototype.rescale = function(nscale) {
+ var cx = this.origin_x + this.width/(2*this.scale);
+ var cy = this.origin_y + this.height/(2*this.scale);
+
+ this.scale = nscale;
+
+ this.origin_x = cx - this.width/(2*this.scale);
+ this.origin_y = cy - this.height/(2*this.scale);
+
+ this.redraw_background();
+ }
+
+ Schematic.prototype.zoomin = function() {
+ this.rescale(this.scale * 1.5);
+ }
+
+ Schematic.prototype.zoomout = function() {
+ this.rescale(this.scale / 1.5);
+ }
+
+ Schematic.prototype.zoomall = function() {
+ // w,h for schematic
+ var sch_w = this.bbox[2] - this.bbox[0];
+ var sch_h = this.bbox[3] - this.bbox[1];
+
+ // compute scales that would make schematic fit, choose smallest
+ var scale_x = this.width/sch_w;
+ var scale_y = this.height/sch_h;
+ this.scale = Math.min(scale_x,scale_y);
+
+ // center the schematic
+ var cx = (this.bbox[2] + this.bbox[0])/2;
+ var cy = (this.bbox[3] + this.bbox[1])/2;
+ this.origin_x = cx - this.width/(2*this.scale);
+ this.origin_y = cy - this.height/(2*this.scale);
+
+ this.redraw_background();
+ }
+
+ Schematic.prototype.cut = function() {
+ // clear previous contents
+ sch_clipboard = [];
+
+ // look for selected components, move them to clipboard.
+ for (var i = this.components.length - 1; i >=0; --i) {
+ var c = this.components[i];
+ if (c.selected) {
+ c.remove();
+ sch_clipboard.push(c);
+ }
+ }
+
+ // update diagram view
+ this.redraw();
+ }
+
+ Schematic.prototype.copy = function() {
+ // clear previous contents
+ sch_clipboard = [];
+
+ // look for selected components, copy them to clipboard.
+ for (var i = this.components.length - 1; i >=0; --i) {
+ var c = this.components[i];
+ if (c.selected)
+ sch_clipboard.push(c.clone(c.x,c.y));
+ }
+ }
+
+ Schematic.prototype.paste = function() {
+ // compute left,top of bounding box for origins of
+ // components in the clipboard
+ var left = undefined;
+ var top = undefined;
+ for (var i = sch_clipboard.length - 1; i >= 0; --i) {
+ var c = sch_clipboard[i];
+ left = left ? Math.min(left,c.x) : c.x;
+ top = top ? Math.min(top,c.y) : c.y;
+ }
+
+ this.message('cursor '+this.cursor_x+','+this.cursor_y);
+
+ // clear current selections
+ this.unselect_all(-1);
+ this.redraw_background(); // so we see any components that got unselected
+
+ // make clones of components on the clipboard, positioning
+ // them relative to the cursor
+ for (var i = sch_clipboard.length - 1; i >= 0; --i) {
+ var c = sch_clipboard[i];
+ var new_c = c.clone(this.cursor_x + (c.x - left),this.cursor_y + (c.y - top));
+ new_c.set_select(true);
+ new_c.add(this);
+ }
+
+ // see what we've wrought
+ this.redraw();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Netlist and Simulation interface
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ // load diagram from JSON representation
+ Schematic.prototype.load_schematic = function(value,initial_value) {
+ // use default value if no schematic info in value
+ if (value == undefined || value.indexOf('[') == -1)
+ value = initial_value;
+
+ if (value && value.indexOf('[') != -1) {
+ // convert string value into data structure
+ var json = JSON.parse(value);
+
+ // top level is a list of components
+ for (var i = json.length - 1; i >= 0; --i) {
+ var c = json[i];
+ if (c[0] == 'view') {
+ // special hack: view component lets us recreate view
+ this.origin_x = c[1];
+ this.origin_y = c[2];
+ this.scale = c[3];
+ //this.ac_npts = c[4];
+ this.ac_fstart = c[5];
+ this.ac_fstop = c[6];
+ this.ac_source_name = c[7];
+ this.tran_npts = c[8];
+ this.tran_tstop = c[9];
+ this.dc_max_iters = c[10];
+ } else if (c[0] == 'w') {
+ // wire
+ this.add_wire(c[1][0],c[1][1],c[1][2],c[1][3]);
+ } else if (c[0] == 'dc') {
+ this.dc_results = c[1];
+ } else if (c[0] == 'transient') {
+ this.transient_results = c[1];
+ } else if (c[0] == 'ac') {
+ this.ac_results = c[1];
+ } else {
+ // ordinary component
+ // c := [type, coords, properties, connections]
+ var type = c[0];
+ var coords = c[1];
+ var properties = c[2];
+
+ // make the part
+ var part = new parts_map[type][0](coords[0],coords[1],coords[2]);
+
+ // give it its properties
+ for (var name in properties)
+ part.properties[name] = properties[name];
+
+ // add component to the diagram
+ part.add(this);
+ }
+ }
+ }
+
+ // see what we've got!
+ this.redraw_background();
+ }
+
+ // label all the nodes in the circuit
+ Schematic.prototype.label_connection_points = function() {
+ // start by clearing all the connection point labels
+ for (var i = this.components.length - 1; i >=0; --i)
+ this.components[i].clear_labels();
+
+ // components are in charge of labeling their unlabeled connections.
+ // labels given to connection points will propagate to coincident connection
+ // points and across Wires.
+
+ // let special components like GND label their connection(s)
+ for (var i = this.components.length - 1; i >=0; --i)
+ this.components[i].add_default_labels();
+
+ // now have components generate labels for unlabeled connections
+ this.next_label = 0;
+ for (var i = this.components.length - 1; i >=0; --i)
+ this.components[i].label_connections();
+ }
+
+ // generate a new label
+ Schematic.prototype.get_next_label = function() {
+ // generate next label in sequence
+ this.next_label += 1;
+ return this.next_label.toString();
+ }
+
+ // propagate label to coincident connection points
+ Schematic.prototype.propagate_label = function(label,location) {
+ var cplist = this.connection_points[location];
+ for (var i = cplist.length - 1; i >= 0; --i)
+ cplist[i].propagate_label(label);
+ }
+
+ // update the value field of our corresponding input field with JSON
+ // representation of schematic
+ Schematic.prototype.update_value = function() {
+ // label connection points
+ this.label_connection_points();
+
+ // build JSON data structure, convert to string value for
+ // input field
+ this.input.value = JSON.stringify(this.json_with_analyses());
+ }
+
+ // produce a JSON representation of the diagram
+ Schematic.prototype.json = function() {
+ var json = [];
+
+ // output all the components/wires in the diagram
+ var n = this.components.length;
+ for (var i = 0; i < n; i++)
+ json.push(this.components[i].json(i));
+
+ // capture the current view parameters
+ json.push(['view',this.origin_x,this.origin_y,this.scale,
+ this.ac_npts,this.ac_fstart,this.ac_fstop,
+ this.ac_source_name,this.tran_npts,this.tran_tstop,
+ this.dc_max_iters]);
+
+ return json;
+ }
+
+ // produce a JSON representation of the diagram
+ Schematic.prototype.json_with_analyses = function() {
+ var json = this.json();
+
+ if (this.dc_results != undefined) json.push(['dc',this.dc_results]);
+ if (this.ac_results != undefined) json.push(['ac',this.ac_results]);
+ if (this.transient_results != undefined) json.push(['transient',this.transient_results]);
+
+ return json;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Simulation interface
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Schematic.prototype.extract_circuit = function() {
+ // give all the circuit nodes a name, extract netlist
+ this.label_connection_points();
+ var netlist = this.json();
+
+ // since we've done the heavy lifting, update input field value
+ // so user can grab diagram if they want
+ this.input.value = JSON.stringify(netlist);
+
+ // create a circuit from the netlist
+ var ckt = new cktsim.Circuit();
+ if (ckt.load_netlist(netlist))
+ return ckt;
+ else
+ return null;
+ }
+
+ Schematic.prototype.dc_analysis = function() {
+ // remove any previous annotations
+ this.unselect_all(-1);
+ this.redraw_background();
+
+ var ckt = this.extract_circuit();
+ if (ckt === null) return;
+
+ // run the analysis
+ this.operating_point = ckt.dc();
+
+ if (this.operating_point != undefined) {
+ // save a copy of the results for submission
+ this.dc_results = {};
+ for (var i in this.operating_point) this.dc_results[i] = this.operating_point[i];
+
+ // display results on diagram
+ this.redraw();
+ }
+ }
+
+ // return a list of [color,node_label,offset,type] for each probe in the diagram
+ // type == 'voltage' or 'current'
+ Schematic.prototype.find_probes = function() {
+ var result = [];
+ var result = [];
+ for (var i = this.components.length - 1; i >= 0; --i) {
+ var c = this.components[i];
+ var info = c.probe_info();
+ if (info != undefined) result.push(c.probe_info());
+ }
+ return result;
+ }
+
+ // use a dialog to get AC analysis parameters
+ Schematic.prototype.setup_ac_analysis = function() {
+ this.unselect_all(-1);
+ this.redraw_background();
+
+ var npts_lbl = 'Number of points/decade';
+ var fstart_lbl = 'Starting frequency (Hz)';
+ var fstop_lbl = 'Ending frequency (Hz)';
+ var source_name_lbl = 'Name of V or I source for ac'
+
+ if (this.find_probes().length == 0) {
+ alert("AC Analysis: there are no voltage probes in the diagram!");
+ return;
+ }
+
+ var fields = new Array();
+ //fields[npts_lbl] = build_input('text',10,this.ac_npts);
+ fields[fstart_lbl] = build_input('text',10,this.ac_fstart);
+ fields[fstop_lbl] = build_input('text',10,this.ac_fstop);
+ fields[source_name_lbl] = build_input('text',10,this.ac_source_name);
+
+ var content = build_table(fields);
+ content.fields = fields;
+ content.sch = this;
+
+ this.dialog('AC Analysis',content,function(content) {
+ var sch = content.sch;
+
+ // retrieve parameters, remember for next time
+ //sch.ac_npts = content.fields[npts_lbl].value;
+ sch.ac_fstart = content.fields[fstart_lbl].value;
+ sch.ac_fstop = content.fields[fstop_lbl].value;
+ sch.ac_source_name = content.fields[source_name_lbl].value;
+
+ sch.ac_analysis(cktsim.parse_number(sch.ac_npts),
+ cktsim.parse_number(sch.ac_fstart),
+ cktsim.parse_number(sch.ac_fstop),
+ sch.ac_source_name);
+ });
+ }
+
+ // perform ac analysis
+ Schematic.prototype.ac_analysis = function(npts,fstart,fstop,ac_source_name) {
+ // run the analysis
+ var ckt = this.extract_circuit();
+ if (ckt === null) return;
+ var results = ckt.ac(npts,fstart,fstop,ac_source_name);
+
+ if (typeof results == 'string')
+ this.message(results);
+ else {
+ var x_values = results['_frequencies_'];
+
+ // x axis will be a log scale
+ for (var i = x_values.length - 1; i >= 0; --i)
+ x_values[i] = Math.log(x_values[i])/Math.LN10;
+
+
+ if (this.submit_analyses != undefined) {
+ var submit = this.submit_analyses['ac'];
+ if (submit != undefined) {
+ // save a copy of the results for submission
+ this.ac_results = {};
+
+ // save requested values for each requested node
+ for (var j = 0; j < submit.length; j++) {
+ var flist = submit[j]; // [node_name,f1,f2,...]
+ var node = flist[0];
+ var values = results[node];
+ var fvlist = [];
+ // for each requested freq, interpolate response value
+ for (var k = 1; k < flist.length; k++) {
+ var f = flist[k];
+ var v = interpolate(f,x_values,values);
+ // convert to dB
+ fvlist.push([f,v == undefined ? 'undefined' : 20.0 * Math.log(v)/Math.LN10]);
+ }
+ // save results as list of [f,response] paris
+ this.ac_results[node] = fvlist;
+ }
+ }
+ }
+
+ // set up plot values for each node with a probe
+ var y_values = []; // list of [color, result_array]
+ var z_values = []; // list of [color, result_array]
+ var probes = this.find_probes();
+
+ var probe_maxv = [];
+ var probe_color = [];
+
+ // Check for probe with near zero transfer function and warn
+ for (var i = probes.length - 1; i >= 0; --i) {
+ if (probes[i][3] != 'voltage') continue;
+ probe_color[i] = probes[i][0];
+ var label = probes[i][1];
+ var v = results[label];
+ probe_maxv[i] = array_max(v); // magnitudes always > 0
+ }
+ var all_max = array_max(probe_maxv);
+
+ if (all_max < 1.0e-16) {
+ alert('Zero ac response, -infinity on DB scale.');
+ } else {
+ for (var i = probes.length - 1; i >= 0; --i) {
+ if (probes[i][3] != 'voltage') continue;
+ if ((probe_maxv[i] / all_max) < 1.0e-10) {
+ alert('Near zero ac response, remove ' + probe_color[i] + ' probe');
+ return;
+ }
+ }
+ }
+
+ for (var i = probes.length - 1; i >= 0; --i) {
+ if (probes[i][3] != 'voltage') continue;
+ var color = probes[i][0];
+ var label = probes[i][1];
+ var offset = cktsim.parse_number(probes[i][2]);
+
+ var v = results[label];
+ // convert values into dB relative to source amplitude
+ var v_max = 1;
+ for (var j = v.length - 1; j >= 0; --j)
+ // convert each value to dB relative to max
+ v[j] = 20.0 * Math.log(v[j]/v_max)/Math.LN10;
+ y_values.push([color,offset,v]);
+
+ var v = results[label+'_phase'];
+ z_values.push([color,0,v]);
+ }
+
+ // graph the result and display in a window
+ var graph2 = this.graph(x_values,'log(Frequency in Hz)',z_values,'degrees');
+ this.window('AC Analysis - Phase',graph2);
+ var graph1 = this.graph(x_values,'log(Frequency in Hz)',y_values,'dB');
+ this.window('AC Analysis - Magnitude',graph1,50);
+ }
+ }
+
+ Schematic.prototype.transient_analysis = function() {
+ this.unselect_all(-1);
+ this.redraw_background();
+
+ var npts_lbl = 'Minimum number of timepoints';
+ var tstop_lbl = 'Stop Time (seconds)';
+
+ var probes = this.find_probes();
+ if (probes.length == 0) {
+ alert("Transient Analysis: there are no probes in the diagram!");
+ return;
+ }
+
+ var fields = new Array();
+ //fields[npts_lbl] = build_input('text',10,this.tran_npts);
+ fields[tstop_lbl] = build_input('text',10,this.tran_tstop);
+
+ var content = build_table(fields);
+ content.fields = fields;
+ content.sch = this;
+
+ this.dialog('Transient Analysis',content,function(content) {
+ var sch = content.sch;
+ var ckt = sch.extract_circuit();
+ if (ckt === null) return;
+
+ // retrieve parameters, remember for next time
+ //sch.tran_npts = content.fields[npts_lbl].value;
+ sch.tran_tstop = content.fields[tstop_lbl].value;
+
+ // gather a list of nodes that are being probed. These
+ // will be added to the list of nodes checked during the
+ // LTE calculations in transient analysis
+ var probe_list = sch.find_probes();
+ var probe_names = new Array(probe_list.length);
+ for (var i = probe_list.length - 1; i >= 0; --i)
+ probe_names[i] = probe_list[i][1];
+
+ // run the analysis
+ var results = ckt.tran(ckt.parse_number(sch.tran_npts), 0,
+ ckt.parse_number(sch.tran_tstop), probe_names, false);
+
+ if (typeof results == 'string')
+ sch.message(results);
+ else {
+ if (sch.submit_analyses != undefined) {
+ var submit = sch.submit_analyses['tran'];
+ if (submit != undefined) {
+ // save a copy of the results for submission
+ sch.transient_results = {};
+ var times = results['_time_'];
+
+ // save requested values for each requested node
+ for (var j = 0; j < submit.length; j++) {
+ var tlist = submit[j]; // [node_name,t1,t2,...]
+ var node = tlist[0];
+ var values = results[node];
+ var tvlist = [];
+ // for each requested time, interpolate waveform value
+ for (var k = 1; k < tlist.length; k++) {
+ var t = tlist[k];
+ var v = interpolate(t,times,values);
+ tvlist.push([t,v == undefined ? 'undefined' : v]);
+ }
+ // save results as list of [t,value] pairs
+ sch.transient_results[node] = tvlist;
+ }
+ }
+ }
+
+ var x_values = results['_time_'];
+ var x_legend = 'Time';
+
+ // set up plot values for each node with a probe
+ var v_values = []; // voltage values: list of [color, result_array]
+ var i_values = []; // current values: list of [color, result_array]
+ var probes = sch.find_probes();
+
+ for (var i = probes.length - 1; i >= 0; --i) {
+ var color = probes[i][0];
+ var label = probes[i][1];
+ var offset = cktsim.parse_number(probes[i][2]);
+ var v = results[label];
+ if (v == undefined) {
+ alert('The ' + color + ' probe is connected to node ' + '"' + label + '"' + ' which is not an actual circuit node');
+ } else if (probes[i][3] == 'voltage') {
+ if (color == 'x-axis') {
+ x_values = v;
+ x_legend = 'Voltage';
+ } else v_values.push([color,offset,v]);
+ } else {
+ if (color == 'x-axis') {
+ x_values = v;
+ x_legend = 'Current';
+ } else i_values.push([color,offset,v]);
+ }
+ }
+
+ // graph the result and display in a window
+ var graph = sch.graph(x_values,x_legend,v_values,'Voltage',i_values,'Current');
+ sch.window('Results of Transient Analysis',graph);
+ }
+ })
+ }
+
+ // t is the time at which we want a value
+ // times is a list of timepoints from the simulation
+ function interpolate(t,times,values) {
+ if (values == undefined) return undefined;
+
+ for (var i = 0; i < times.length; i++)
+ if (t < times[i]) {
+ // t falls between times[i-1] and times[i]
+ var t1 = (i == 0) ? times[0] : times[i-1];
+ var t2 = times[i];
+
+ if (t2 == undefined) return undefined;
+
+ var v1 = (i == 0) ? values[0] : values[i-1];
+ var v2 = values[i];
+ var v = v1;
+ if (t != t1) v += (t - t1)*(v2 - v1)/(t2 - t1);
+ return v;
+ }
+ }
+
+ // external interface for setting the property value of a named component
+ Schematic.prototype.set_property = function(component_name,property,value) {
+ this.unselect_all(-1);
+
+ for (var i = this.components.length - 1; i >= 0; --i) {
+ var component = this.components[i];
+ if (component.properties['name'] == component_name) {
+ component.properties[property] = value.toString();
+ break;
+ }
+ }
+
+ // update diagram
+ this.redraw_background();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Drawing support -- deals with scaling and scrolling of diagrama
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ // here to redraw background image containing static portions of the schematic.
+ // Also redraws dynamic portion.
+ Schematic.prototype.redraw_background = function() {
+ var c = this.bg_image.getContext('2d');
+
+ c.lineCap = 'round';
+
+ // paint background color
+ c.fillStyle = element_style;
+ c.fillRect(0,0,this.width,this.height);
+
+ if (!this.diagram_only) {
+ // grid
+ c.strokeStyle = grid_style;
+ var first_x = this.origin_x;
+ var last_x = first_x + this.width/this.scale;
+ var first_y = this.origin_y;
+ var last_y = first_y + this.height/this.scale;
+
+ for (var i = this.grid*Math.ceil(first_x/this.grid); i < last_x; i += this.grid)
+ this.draw_line(c,i,first_y,i,last_y,0.1);
+
+ for (var i = this.grid*Math.ceil(first_y/this.grid); i < last_y; i += this.grid)
+ this.draw_line(c,first_x,i,last_x,i,0.1);
+ }
+
+ // unselected components
+ var min_x = Infinity; // compute bounding box for diagram
+ var max_x = -Infinity;
+ var min_y = Infinity;
+ var max_y = -Infinity;
+ for (var i = this.components.length - 1; i >= 0; --i) {
+ var component = this.components[i];
+ if (!component.selected) {
+ component.draw(c);
+ min_x = Math.min(component.bbox[0],min_x);
+ max_x = Math.max(component.bbox[2],max_x);
+ min_y = Math.min(component.bbox[1],min_y);
+ max_y = Math.max(component.bbox[3],max_y);
+ }
+ }
+ this.unsel_bbox = [min_x,min_y,max_x,max_y];
+
+ this.redraw(); // background changed, redraw on screen
+ }
+
+ // redraw what user sees = static image + dynamic parts
+ Schematic.prototype.redraw = function() {
+ var c = this.canvas.getContext('2d');
+
+ // put static image in the background
+ c.drawImage(this.bg_image, 0, 0);
+
+ // selected components
+ var min_x = this.unsel_bbox[0]; // compute bounding box for diagram
+ var max_x = this.unsel_bbox[2];
+ var min_y = this.unsel_bbox[1];
+ var max_y = this.unsel_bbox[3];
+ var selections = false;
+ for (var i = this.components.length - 1; i >= 0; --i) {
+ var component = this.components[i];
+ if (component.selected) {
+ component.draw(c);
+ selections = true;
+ min_x = Math.min(component.bbox[0],min_x);
+ max_x = Math.max(component.bbox[2],max_x);
+ min_y = Math.min(component.bbox[1],min_y);
+ max_y = Math.max(component.bbox[3],max_y);
+ }
+ }
+ this.enable_tool('cut',selections);
+ this.enable_tool('copy',selections);
+ this.enable_tool('paste',sch_clipboard.length > 0);
+
+ // include a margin for diagram bounding box
+ var dx = (max_x - min_x)/4;
+ var dy = (max_y - min_y)/4;
+ this.bbox = [min_x - dx,min_y - dy,max_x + dx,max_y + dy];
+
+ // connection points: draw one at each location
+ for (var location in this.connection_points) {
+ var cplist = this.connection_points[location];
+ cplist[0].draw(c,cplist.length);
+ }
+
+ // draw new wire
+ if (this.wire) {
+ var r = this.wire;
+ c.strokeStyle = selected_style;
+ this.draw_line(c,r[0],r[1],r[2],r[3],1);
+ }
+
+ // draw selection rectangle
+ if (this.select_rect) {
+ var r = this.select_rect;
+ c.lineWidth = 1;
+ c.strokeStyle = selected_style;
+ c.beginPath();
+ c.moveTo(r[0],r[1]);
+ c.lineTo(r[0],r[3]);
+ c.lineTo(r[2],r[3]);
+ c.lineTo(r[2],r[1]);
+ c.lineTo(r[0],r[1]);
+ c.stroke();
+ }
+
+ // display operating point results
+ if (this.operating_point) {
+ if (typeof this.operating_point == 'string')
+ this.message(this.operating_point);
+ else {
+ // make a copy of the operating_point info so we can mess with it
+ var temp = new Array();
+ for (var i in this.operating_point) temp[i] = this.operating_point[i];
+
+ // run through connection points displaying (once) the voltage
+ // for each electrical node
+ for (var location in this.connection_points)
+ (this.connection_points[location])[0].display_voltage(c,temp);
+
+ // let components display branch current info if available
+ for (var i = this.components.length - 1; i >= 0; --i)
+ this.components[i].display_current(c,temp)
+ }
+ }
+
+ // finally overlay cursor
+ if (this.drawCursor && this.draw_cursor) {
+ //var x = this.cursor_x;
+ //var y = this.cursor_y;
+ //this.draw_text(c,'('+x+','+y+')',x+this.grid,y-this.grid,10);
+ this.draw_cursor(c,this.cursor_x,this.cursor_y);
+ }
+ }
+
+ // draws a cross cursor
+ Schematic.prototype.cross_cursor = function(c,x,y) {
+ this.draw_line(c,x-this.grid,y,x+this.grid,y,1);
+ this.draw_line(c,x,y-this.grid,x,y+this.grid,1);
+ }
+
+ Schematic.prototype.moveTo = function(c,x,y) {
+ c.moveTo((x - this.origin_x) * this.scale,(y - this.origin_y) * this.scale);
+ }
+
+ Schematic.prototype.lineTo = function(c,x,y) {
+ c.lineTo((x - this.origin_x) * this.scale,(y - this.origin_y) * this.scale);
+ }
+
+ Schematic.prototype.draw_line = function(c,x1,y1,x2,y2,width) {
+ c.lineWidth = width*this.scale;
+ c.beginPath();
+ c.moveTo((x1 - this.origin_x) * this.scale,(y1 - this.origin_y) * this.scale);
+ c.lineTo((x2 - this.origin_x) * this.scale,(y2 - this.origin_y) * this.scale);
+ c.stroke();
+ }
+
+ Schematic.prototype.draw_arc = function(c,x,y,radius,start_radians,end_radians,anticlockwise,width,filled) {
+ c.lineWidth = width*this.scale;
+ c.beginPath();
+ c.arc((x - this.origin_x)*this.scale,(y - this.origin_y)*this.scale,radius*this.scale,
+ start_radians,end_radians,anticlockwise);
+ if (filled) c.fill();
+ else c.stroke();
+ }
+
+ Schematic.prototype.draw_text = function(c,text,x,y,size) {
+ c.font = size*this.scale+'pt sans-serif'
+ c.fillText(text,(x - this.origin_x) * this.scale,(y - this.origin_y) * this.scale);
+ }
+
+ // add method to canvas to compute relative coords for event
+ HTMLCanvasElement.prototype.relMouseCoords = function(event){
+ // run up the DOM tree to figure out coords for top,left of canvas
+ var totalOffsetX = 0;
+ var totalOffsetY = 0;
+ var currentElement = this;
+ do {
+ totalOffsetX += currentElement.offsetLeft;
+ totalOffsetY += currentElement.offsetTop;
+ }
+ while (currentElement = currentElement.offsetParent);
+
+ // now compute relative position of click within the canvas
+ this.mouse_x = event.pageX - totalOffsetX;
+ this.mouse_y = event.pageY - totalOffsetY;
+
+ this.page_x = event.pageX;
+ this.page_y = event.pageY;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Event handling
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ // process keystrokes, consuming those that are meaningful to us
+ function schematic_key_down(event) {
+ if (!event) event = window.event;
+ var sch = (window.event) ? event.srcElement.schematic : event.target.schematic;
+ var code = event.keyCode;
+
+ // keep track of modifier key state
+ if (code == 16) sch.shiftKey = true;
+ else if (code == 17) sch.ctrlKey = true;
+ else if (code == 18) sch.altKey = true;
+ else if (code == 91) sch.cmdKey = true;
+
+ // backspace or delete: delete selected components
+ else if (code == 8 || code == 46) {
+ // delete selected components
+ for (var i = sch.components.length - 1; i >= 0; --i) {
+ var component = sch.components[i];
+ if (component.selected) component.remove();
+ }
+ sch.clean_up_wires();
+ sch.redraw_background();
+ event.preventDefault();
+ return false;
+ }
+
+ // cmd/ctrl x: cut
+ else if ((sch.ctrlKey || sch.cmdKey) && code == 88) {
+ sch.cut();
+ event.preventDefault();
+ return false;
+ }
+
+ // cmd/ctrl c: copy
+ else if ((sch.ctrlKey || sch.cmdKey) && code == 67) {
+ sch.copy();
+ event.preventDefault();
+ return false;
+ }
+
+ // cmd/ctrl v: paste
+ else if ((sch.ctrlKey || sch.cmdKey) && code == 86) {
+ sch.paste();
+ event.preventDefault();
+ return false;
+ }
+
+ // 'r': rotate component
+ else if (!sch.ctrlKey && !sch.altKey && !sch.cmdKey && code == 82) {
+ // rotate
+ for (var i = sch.components.length - 1; i >= 0; --i) {
+ var component = sch.components[i];
+ if (component.selected) {
+ component.rotate(1);
+ sch.check_wires(component);
+ }
+ }
+ sch.clean_up_wires();
+ sch.redraw_background();
+ event.preventDefault();
+ return false;
+ }
+
+ else return true;
+
+ // consume keystroke
+ sch.redraw();
+ event.preventDefault();
+ return false;
+ }
+
+ function schematic_key_up(event) {
+ if (!event) event = window.event;
+ var sch = (window.event) ? event.srcElement.schematic : event.target.schematic;
+ var code = event.keyCode;
+
+ if (code == 16) sch.shiftKey = false;
+ else if (code == 17) sch.ctrlKey = false;
+ else if (code == 18) sch.altKey = false;
+ else if (code == 91) sch.cmdKey = false;
+ }
+
+ function schematic_mouse_enter(event) {
+ if (!event) event = window.event;
+ var sch = (window.event) ? event.srcElement.schematic : event.target.schematic;
+
+ // see if user has selected a new part
+ if (sch.new_part) {
+ // grab incoming part, turn off selection of parts bin
+ var part = sch.new_part;
+ sch.new_part = undefined;
+ part.select(false);
+
+ // unselect everything else in the schematic, add part and select it
+ sch.unselect_all(-1);
+ sch.redraw_background(); // so we see any components that got unselected
+
+ // make a clone of the component in the parts bin
+ part = part.component.clone(sch.cursor_x,sch.cursor_y);
+ part.add(sch); // add it to schematic
+ part.set_select(true);
+
+ // and start dragging it
+ sch.drag_begin();
+ }
+
+ sch.drawCursor = true;
+ sch.redraw();
+ sch.canvas.focus(); // capture key strokes
+ return false;
+ }
+
+ function schematic_mouse_leave(event) {
+ if (!event) event = window.event;
+ var sch = (window.event) ? event.srcElement.schematic : event.target.schematic;
+ sch.drawCursor = false;
+ sch.redraw();
+ return false;
+ }
+
+ function schematic_mouse_down(event) {
+ if (!event) event = window.event;
+ else event.preventDefault();
+ var sch = (window.event) ? event.srcElement.schematic : event.target.schematic;
+
+ // determine where event happened in schematic coordinates
+ sch.canvas.relMouseCoords(event);
+ var x = sch.canvas.mouse_x/sch.scale + sch.origin_x;
+ var y = sch.canvas.mouse_y/sch.scale + sch.origin_y;
+ sch.cursor_x = Math.round(x/sch.grid) * sch.grid;
+ sch.cursor_y = Math.round(y/sch.grid) * sch.grid;
+
+ // is mouse over a connection point? If so, start dragging a wire
+ var cplist = sch.connection_points[sch.cursor_x + ',' + sch.cursor_y];
+ if (cplist && !event.shiftKey) {
+ sch.unselect_all(-1);
+ sch.wire = [sch.cursor_x,sch.cursor_y,sch.cursor_x,sch.cursor_y];
+ } else {
+ // give all components a shot at processing the selection event
+ var which = -1;
+ for (var i = sch.components.length - 1; i >= 0; --i)
+ if (sch.components[i].select(x,y,event.shiftKey)) {
+ if (sch.components[i].selected) {
+ sch.drag_begin();
+ which = i; // keep track of component we found
+ }
+ break;
+ }
+ // did we just click on a previously selected component?
+ var reselect = which!=-1 && sch.components[which].was_previously_selected;
+
+ if (!event.shiftKey) {
+ // if shift key isn't pressed and we didn't click on component
+ // that was already selected, unselect everyone except component
+ // we just clicked on
+ if (!reselect) sch.unselect_all(which);
+
+ // if there's nothing to drag, set up a selection rectangle
+ if (!sch.dragging) sch.select_rect = [sch.canvas.mouse_x,sch.canvas.mouse_y,
+ sch.canvas.mouse_x,sch.canvas.mouse_y];
+ }
+ }
+
+ sch.redraw_background();
+ return false;
+ }
+
+ function schematic_mouse_move(event) {
+ if (!event) event = window.event;
+ var sch = (window.event) ? event.srcElement.schematic : event.target.schematic;
+
+ sch.canvas.relMouseCoords(event);
+ var x = sch.canvas.mouse_x/sch.scale + sch.origin_x;
+ var y = sch.canvas.mouse_y/sch.scale + sch.origin_y;
+ sch.cursor_x = Math.round(x/sch.grid) * sch.grid;
+ sch.cursor_y = Math.round(y/sch.grid) * sch.grid;
+
+ if (sch.wire) {
+ // update new wire end point
+ sch.wire[2] = sch.cursor_x;
+ sch.wire[3] = sch.cursor_y;
+ } else if (sch.dragging) {
+ // see how far we moved
+ var dx = sch.cursor_x - sch.drag_x;
+ var dy = sch.cursor_y - sch.drag_y;
+ if (dx != 0 || dy != 0) {
+ // update position for next time
+ sch.drag_x = sch.cursor_x;
+ sch.drag_y = sch.cursor_y;
+
+ // give all components a shot at processing the event
+ for (var i = sch.components.length - 1; i >= 0; --i) {
+ var component = sch.components[i];
+ if (component.selected) component.move(dx,dy);
+ }
+ }
+ } else if (sch.select_rect) {
+ // update moving corner of selection rectangle
+ sch.select_rect[2] = sch.canvas.mouse_x;
+ sch.select_rect[3] = sch.canvas.mouse_y;
+ //sch.message(sch.select_rect.toString());
+ }
+
+ // just redraw dynamic components
+ sch.redraw();
+ //sch.message(sch.canvas.page_x + ',' + sch.canvas.page_y + ';' + sch.canvas.mouse_x + ',' + sch.canvas.mouse_y + ';' + sch.cursor_x + ',' + sch.cursor_y);
+
+ return false;
+ }
+
+ function schematic_mouse_up(event) {
+ if (!event) event = window.event;
+ else event.preventDefault();
+ var sch = (window.event) ? event.srcElement.schematic : event.target.schematic;
+
+ // drawing a new wire
+ if (sch.wire) {
+ var r = sch.wire;
+ sch.wire = undefined;
+
+ if (r[0]!=r[2] || r[1]!=r[3]) {
+ // insert wire component
+ sch.add_wire(r[0],r[1],r[2],r[3]);
+ sch.clean_up_wires();
+ sch.redraw_background();
+ } else sch.redraw();
+ }
+
+ // dragging
+ if (sch.dragging) sch.drag_end();
+
+ // selection rectangle
+ if (sch.select_rect) {
+ var r = sch.select_rect;
+
+ // if select_rect is a point, we've already dealt with selection
+ // in mouse_down handler
+ if (r[0]!=r[2] || r[1]!=r[3]) {
+ // convert to schematic coordinates
+ var s = [r[0]/sch.scale + sch.origin_x, r[1]/sch.scale + sch.origin_y,
+ r[2]/sch.scale + sch.origin_x, r[3]/sch.scale + sch.origin_y];
+ canonicalize(s);
+
+ if (!event.shiftKey) sch.unselect_all();
+
+ // select components that intersect selection rectangle
+ for (var i = sch.components.length - 1; i >= 0; --i)
+ sch.components[i].select_rect(s,event.shiftKey);
+ }
+
+ sch.select_rect = undefined;
+ sch.redraw_background();
+ }
+ return false;
+ }
+
+ function schematic_double_click(event) {
+ if (!event) event = window.event;
+ else event.preventDefault();
+ var sch = (window.event) ? event.srcElement.schematic : event.target.schematic;
+
+ // determine where event happened in schematic coordinates
+ sch.canvas.relMouseCoords(event);
+ var x = sch.canvas.mouse_x/sch.scale + sch.origin_x;
+ var y = sch.canvas.mouse_y/sch.scale + sch.origin_y;
+ sch.cursor_x = Math.round(x/sch.grid) * sch.grid;
+ sch.cursor_y = Math.round(y/sch.grid) * sch.grid;
+
+ // see if we double-clicked a component. If so, edit it's properties
+ for (var i = sch.components.length - 1; i >= 0; --i)
+ if (sch.components[i].edit_properties(x,y))
+ break;
+
+ return false;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Status message and dialogs
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Schematic.prototype.message = function(message) {
+ this.status.nodeValue = message;
+ }
+
+ Schematic.prototype.append_message = function(message) {
+ this.status.nodeValue += ' / '+message;
+ }
+
+ // set up a dialog with specified title, content and two buttons at
+ // the bottom: OK and Cancel. If Cancel is clicked, dialog goes away
+ // and we're done. If OK is clicked, dialog goes away and the
+ // callback function is called with the content as an argument (so
+ // that the values of any fields can be captured).
+ Schematic.prototype.dialog = function(title,content,callback) {
+ // create the div for the top level of the dialog, add to DOM
+ var dialog = document.createElement('div');
+ dialog.sch = this;
+ dialog.content = content;
+ dialog.callback = callback;
+
+ // look for property input fields in the content and give
+ // them a keypress listener that interprets ENTER as
+ // clicking OK.
+ var plist = content.getElementsByClassName('property');
+ for (var i = plist.length - 1; i >= 0; --i) {
+ var field = plist[i];
+ field.dialog = dialog; // help event handler find us...
+ field.addEventListener('keypress',dialog_check_for_ENTER,false);
+ }
+
+ // div to hold the content
+ var body = document.createElement('div');
+ content.style.marginBotton = '5px';
+ body.appendChild(content);
+ body.style.padding = '5px';
+ dialog.appendChild(body);
+
+ // OK button
+ var ok_button = document.createElement('span');
+ ok_button.appendChild(document.createTextNode('OK'));
+ ok_button.dialog = dialog; // for the handler to use
+ ok_button.addEventListener('click',dialog_okay,false);
+ ok_button.style.display = 'inline';
+ ok_button.style.border = '1px solid';
+ ok_button.style.padding = '5px';
+ ok_button.style.margin = '10px';
+
+ // cancel button
+ var cancel_button = document.createElement('span');
+ cancel_button.appendChild(document.createTextNode('Cancel'));
+ cancel_button.dialog = dialog; // for the handler to use
+ cancel_button.addEventListener('click',dialog_cancel,false);
+ cancel_button.style.display = 'inline';
+ cancel_button.style.border = '1px solid';
+ cancel_button.style.padding = '5px';
+ cancel_button.style.margin = '10px';
+
+ // div to hold the two buttons
+ var buttons = document.createElement('div');
+ buttons.style.textAlign = 'center';
+ buttons.appendChild(ok_button);
+ buttons.appendChild(cancel_button);
+ buttons.style.padding = '5px';
+ buttons.style.margin = '10px';
+ dialog.appendChild(buttons);
+
+ // put into an overlay window
+ this.window(title,dialog);
+ }
+
+ // callback when user click "Cancel" in a dialog
+ function dialog_cancel(event) {
+ if (!event) event = window.event;
+ var dialog = (window.event) ? event.srcElement.dialog : event.target.dialog;
+
+ window_close(dialog.win);
+ }
+
+ // callback when user click "OK" in a dialog
+ function dialog_okay(event) {
+ if (!event) event = window.event;
+ var dialog = (window.event) ? event.srcElement.dialog : event.target.dialog;
+
+ window_close(dialog.win);
+
+ // invoke the callback with the dialog contents as the argument
+ if (dialog.callback) dialog.callback(dialog.content);
+ }
+
+ // callback for keypress in input fields: if user typed ENTER, act
+ // like they clicked OK button.
+ function dialog_check_for_ENTER(event) {
+ var key = (window.event) ? window.event.keyCode : event.keyCode;
+ if (key == 13) dialog_okay(event);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Draggable, resizeable, closeable window
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ // build a 2-column HTML table from an associative array (keys as text in
+ // column 1, values in column 2).
+ function build_table(a) {
+ var tbl = document.createElement('table');
+
+ // build a row for each element in associative array
+ for (var i in a) {
+ var label = document.createTextNode(i + ': ');
+ var col1 = document.createElement('td');
+ col1.appendChild(label);
+ var col2 = document.createElement('td');
+ col2.appendChild(a[i]);
+ var row = document.createElement('tr');
+ row.appendChild(col1);
+ row.appendChild(col2);
+ row.style.verticalAlign = 'center';
+ tbl.appendChild(row);
+ }
+
+ return tbl;
+ }
+
+ // build an input field
+ function build_input(type,size,value) {
+ var input = document.createElement('input');
+ input.type = type;
+ input.size = size;
+ input.className = 'property'; // make this easier to find later
+ if (value == undefined) input.value = '';
+ else input.value = value.toString();
+ return input;
+ }
+
+ // build a select widget using the strings found in the options array
+ function build_select(options,selected) {
+ var select = document.createElement('select');
+ for (var i = 0; i < options.length; i++) {
+ var option = document.createElement('option');
+ option.text = options[i];
+ select.add(option);
+ if (options[i] == selected) select.selectedIndex = i;
+ }
+ return select;
+ }
+
+ Schematic.prototype.window = function(title,content,offset) {
+ // create the div for the top level of the window
+ var win = document.createElement('div');
+ win.sch = this;
+ win.content = content;
+ win.drag_x = undefined;
+ win.draw_y = undefined;
+
+ // div to hold the title
+ var head = document.createElement('div');
+ head.style.backgroundColor = 'black';
+ head.style.color = 'white';
+ head.style.textAlign = 'center';
+ head.style.padding = '5px';
+ head.appendChild(document.createTextNode(title));
+ head.win = win;
+ win.head = head;
+
+ var close_button = new Image();
+ close_button.src = close_icon;
+ close_button.style.cssFloat = 'right';
+ close_button.addEventListener('click',window_close_button,false);
+ close_button.win = win;
+ head.appendChild(close_button);
+
+ win.appendChild(head);
+
+ // capture mouse events in title bar
+ head.addEventListener('mousedown',window_mouse_down,false);
+
+ // div to hold the content
+ //var body = document.createElement('div');
+ //body.appendChild(content);
+ win.appendChild(content);
+ content.win = win; // so content can contact us
+
+ // compute location relative to canvas
+ if (offset == undefined) offset = 0;
+ win.left = this.canvas.mouse_x + offset;
+ win.top = this.canvas.mouse_y + offset;
+
+ // add to DOM
+ win.style.background = 'white';
+ //win.style.zIndex = '1000';
+ win.style.position = 'absolute';
+ win.style.left = win.left + 'px';
+ win.style.top = win.top + 'px';
+ win.style.border = '2px solid';
+
+ this.canvas.parentNode.insertBefore(win,this.canvas);
+ bring_to_front(win,true);
+ }
+
+ // adjust zIndex of pop-up window so that it is in front
+ function bring_to_front(win,insert) {
+ var wlist = win.sch.window_list;
+ var i = wlist.indexOf(win);
+
+ // remove from current position (if any) in window list
+ if (i != -1) wlist.splice(i,1);
+
+ // if requested, add to end of window list
+ if (insert) wlist.push(win);
+
+ // adjust all zIndex values
+ for (i = 0; i < wlist.length; i += 1)
+ wlist[i].style.zIndex = 1000 + i;
+ }
+
+ // close the window
+ function window_close(win) {
+ // remove the window from the top-level div of the schematic
+ win.parentNode.removeChild(win);
+
+ // remove from list of pop-up windows
+ bring_to_front(win,false);
+ }
+
+ function window_close_button(event) {
+ if (!event) event = window.event;
+ var src = (window.event) ? event.srcElement : event.target;
+ window_close(src.win);
+ }
+
+ // capture mouse events in title bar of window
+ function window_mouse_down(event) {
+ if (!event) event = window.event;
+ var src = (window.event) ? event.srcElement : event.target;
+ var win = src.win;
+
+ bring_to_front(win,true);
+
+ // add handlers to document so we capture them no matter what
+ document.addEventListener('mousemove',window_mouse_move,false);
+ document.addEventListener('mouseup',window_mouse_up,false);
+ document.tracking_window = win;
+
+ // remember where mouse is so we can compute dx,dy during drag
+ win.drag_x = event.pageX;
+ win.drag_y = event.pageY;
+
+ return false;
+ }
+
+ function window_mouse_up(event) {
+ var win = document.tracking_window;
+
+ // show's over folks...
+ document.removeEventListener('mousemove',window_mouse_move,false);
+ document.removeEventListener('mouseup',window_mouse_up,false);
+ document.tracking_window = undefined;
+ win.drag_x = undefined;
+ win.drag_y = undefined;
+ return true; // consume event
+ }
+
+ function window_mouse_move(event) {
+ var win = document.tracking_window;
+
+ if (win.drag_x) {
+ var dx = event.pageX - win.drag_x;
+ var dy = event.pageY - win.drag_y;
+
+ // move the window
+ win.left += dx;
+ win.top += dy;
+ win.style.left = win.left + 'px';
+ win.style.top = win.top + 'px';
+
+ // update reference point
+ win.drag_x += dx;
+ win.drag_y += dy;
+
+ return true; // consume event
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Toolbar
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ Schematic.prototype.add_tool = function(icon,tip,callback) {
+ var tool;
+ if (icon.search('data:image') != -1) {
+ tool = document.createElement('img');
+ tool.src = icon;
+ } else {
+ tool = document.createElement('span');
+ tool.style.font = 'small-caps small sans-serif';
+ var label = document.createTextNode(icon);
+ tool.appendChild(label);
+ }
+
+ // decorate tool
+ tool.style.borderWidth = '1px';
+ tool.style.borderStyle = 'solid';
+ tool.style.borderColor = background_style;
+ tool.style.padding = '2px';
+
+ // set up event processing
+ tool.addEventListener('mouseover',tool_enter,false);
+ tool.addEventListener('mouseout',tool_leave,false);
+ tool.addEventListener('click',tool_click,false);
+
+ // add to toolbar
+ tool.sch = this;
+ tool.tip = tip;
+ tool.callback = callback;
+ this.toolbar.push(tool);
+
+ tool.enabled = false;
+ tool.style.opacity = 0.2;
+
+ return tool;
+ }
+
+ Schematic.prototype.enable_tool = function(tname,which) {
+ var tool = this.tools[tname];
+
+ if (tool != undefined) {
+ tool.style.opacity = which ? 1.0 : 0.2;
+ tool.enabled = which;
+
+ // if disabling tool, remove border and tip
+ if (!which) {
+ tool.style.borderColor = background_style;
+ tool.sch.message('');
+ }
+ }
+ }
+
+ // highlight tool button by turning on border, changing background
+ function tool_enter(event) {
+ if (!event) event = window.event;
+ var tool = (window.event) ? event.srcElement : event.target;
+
+ if (tool.enabled) {
+ tool.style.borderColor = normal_style;
+ tool.sch.message(tool.tip);
+ tool.opacity = 1.0;
+ }
+ }
+
+ // unhighlight tool button by turning off border, reverting to normal background
+ function tool_leave(event) {
+ if (!event) event = window.event;
+ var tool = (window.event) ? event.srcElement : event.target;
+
+ if (tool.enabled) {
+ tool.style.borderColor = background_style;
+ tool.sch.message('');
+ }
+ }
+
+ // handle click on a tool
+ function tool_click(event) {
+ if (!event) event = window.event;
+ var tool = (window.event) ? event.srcElement : event.target;
+
+ if (tool.enabled) {
+ tool.sch.canvas.relMouseCoords(event); // so we can position pop-up window correctly
+ tool.callback.call(tool.sch);
+ }
+ }
+
+ help_icon = 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///wAAAAAAACH5BAkAAAIAIf8LSUNDUkdCRzEwMTL/AAAHqGFwcGwCIAAAbW50clJHQiBYWVogB9kAAgAZAAsAGgALYWNzcEFQUEwAAAAAYXBwbAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALZGVzYwAAAQgAAABvZHNjbQAAAXgAAAVsY3BydAAABuQAAAA4d3RwdAAABxwAAAAUclhZWgAABzAAAAAUZ1hZWgAAB0QAAAAUYlhZWgAAB1gAAAAUclRSQwAAB2wAAAAOY2hhZAAAB3wAAAAsYlRSQwAAB2wAAAAOZ1RS/0MAAAdsAAAADmRlc2MAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB4AAAAMc2tTSwAAACgAAAF4aHJIUgAAACgAAAGgY2FFUwAAACQAAAHIcHRCUgAAACYAAAHsdWtVQQAAACoAAAISZnJGVQAAACgAAAI8emhUVwAAABYAAAJkaXRJVAAAACgAAAJ6bmJOTwAAACYAAAKia29LUgAAABYAAP8CyGNzQ1oAAAAiAAAC3mhlSUwAAAAeAAADAGRlREUAAAAsAAADHmh1SFUAAAAoAAADSnN2U0UAAAAmAAAConpoQ04AAAAWAAADcmphSlAAAAAaAAADiHJvUk8AAAAkAAADomVsR1IAAAAiAAADxnB0UE8AAAAmAAAD6G5sTkwAAAAoAAAEDmVzRVMAAAAmAAAD6HRoVEgAAAAkAAAENnRyVFIAAAAiAAAEWmZpRkkAAAAoAAAEfHBsUEwAAAAsAAAEpHJ1UlUAAAAiAAAE0GFyRUcAAAAmAAAE8mVuVVMAAAAmAAAFGGRhREsAAAAuAAAFPgBWAWEAZQBvAGIAZQD/YwBuAP0AIABSAEcAQgAgAHAAcgBvAGYAaQBsAEcAZQBuAGUAcgBpAQ0AawBpACAAUgBHAEIAIABwAHIAbwBmAGkAbABQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBlAG4A6AByAGkAYwBQAGUAcgBmAGkAbAAgAFIARwBCACAARwBlAG4A6QByAGkAYwBvBBcEMAQzBDAEOwRMBD0EOAQ5ACAEPwRABD4ERAQwBDkEOwAgAFIARwBCAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1AGUAIABSAFYAQpAadSgAIABSAEcAQgAggnJfaWPPj/AAUAByAG8AZgBp/wBsAG8AIABSAEcAQgAgAGcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBzAGsAIABSAEcAQgAtAHAAcgBvAGYAaQBsx3y8GAAgAFIARwBCACDVBLhc0wzHfABPAGIAZQBjAG4A/QAgAFIARwBCACAAcAByAG8AZgBpAGwF5AXoBdUF5AXZBdwAIABSAEcAQgAgBdsF3AXcBdkAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbADBAGwAdABhAGwA4QBuAG8AcwAgAFIARwBCACAAcAByAG8AZgBpAGxmbpAaACAAUgBHAEIAIGPPj//wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrAFAAcgBvAGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjA5MDtQO9A7kDugPMACADwAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBlAG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBHAGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGX/AGkAbgBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBpAGwAaQBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAFIARwBCBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgZFBkQGQQAgBioGOQYxBkoGQQAgAFIARwBCACAGJwZEBjkGJwZFAEcAZQBuAGUAcgBpAGMAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGUARwBlAG4AZQByAGUAbAAgAFIARwBCAC0AYgBlAHMAawByAGkAdgBlAGwAcwBldGV4dAAAAABDb3B5cmlnaHQgMjAwrzcgQXBwbGUgSW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2AAAAAAAAAAEBzQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGwALAAAAAAQABAAAAIglI+pwK3XInhSLoZc0oa/7lHRB4bXRJZoaqau+o6ujBQAOw==';
+
+ cut_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAAQu8MhJqz1g5qs7lxv2gRkQfuWomarXEgDRHjJhf3YtyRav0xcfcFgR0nhB5OwTAQA7';
+
+ copy_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAAQ+8MhJ6wE4Wwqef9gmdV8HiKZJrCz3ecS7TikWfzExvk+M9a0a4MbTkXCgTMeoHPJgG5+yF31SLazsTMTtViIAOw==';
+
+ paste_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAARL8MhJqwUYWJnxWp3GDcgAgCdQIqLKXmVLhhnyHiqpr7rME8AgocVDEB5IJHD0SyofBFzxGIQGAbvB0ZkcTq1CKK6z5YorwnR0w44AADs=';
+
+ close_icon = 'data:image/gif;base64,R0lGODlhEAAQAMQAAGtra/f3/62tre/v9+bm787O1pycnHNzc6WlpcXFxd7e3tbW1nt7e7W1te/v74SEhMXFzmNjY+bm5v///87OzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAQABAAAAVt4DRMZGmSwRQQBUS9MAwRIyQ5Uq7neEFSDtxOF4T8cobIQaE4RAQ5yjHHiCCSD510QtFGvoCFdppDfBu7bYzy+D7WP5ggAgA8Y3FKwi5IAhIweW1vbBGEWy5rilsFi2tGAwSJixAFBCkpJ5ojIQA7';
+
+ zoomall_icon = 'data:image/gif;base64,R0lGODlhEAAQAMT/AAAAAP///zAwYT09bpGRqZ6et5iYsKWlvbi40MzM5cXF3czM5OHh5tTU2fDw84uMom49DbWKcfLy8g0NDcDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABQALAAAAAAQABAAAAVZICWOZFlOwCQF5pg2TDMJbDs1DqI8g2TjOsSC0DMBGEGF4UAz3RQ6wiFRLEkmj8WyUC0FBAMpNdWiBCQD8DWCKq98lEkEAiiTAJB53S7Cz/kuECuAIzWEJCEAIf5PQ29weXJpZ2h0IDIwMDAgYnkgU3VuIE1pY3Jvc3lzdGVtcywgSW5jLiBBbGwgUmlnaHRzIFJlc2VydmVkLg0KSkxGIEdSIFZlciAxLjANCgA7';
+
+ zoomin_icon = 'data:image/gif;base64,R0lGODlhEAAQAMT/AAAAAP///zAwYT09boSEnIqKopiYsJ6etqurxL+/18XF3dnZ8sXF0OHh5tTU2ePj5piZr2EwAMKXfg0NDcDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABQALAAAAAAQABAAAAVXICWOZFkCE2CWaeMwwLCKQPNMBCQEa/0UAEXiIFhNHKmkYcA7MQgKwMGw2PUgiYkBsWuWBoJpNTWjBATgAECCKgfelHVkUh5NIpJ5XXTP7/kRcH9mgyUhADshACH+T0NvcHlyaWdodCAyMDAwIGJ5IFN1biBNaWNyb3N5c3RlbXMsIEluYy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC4NCkpMRiBHUiBWZXIgMS4wDQoAOw==';
+
+ zoomout_icon = 'data:image/gif;base64,R0lGODlhEAAQAMT/AAAAAP///zAwYT09bn19lYSEnJGRqZ6et5iYsJ6etqWlvbi40MzM5cXF3czM5Li4w+Hh5tTU2fDw84uMom49DbWKcQ0NDcDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABcALAAAAAAQABAAAAVX4CWOZFlagGWWaQQ9lrCKViQVxjQEay0RjYXDMFgBIKmkQsA7PQyLhEHB2PUmDoTisGuWBINpNTW7BAbggKWCKgfelzUFUB4BKJV5XXTP7/kUcH9mgyUhADshACH+T0NvcHlyaWdodCAyMDAwIGJ5IFN1biBNaWNyb3N5c3RlbXMsIEluYy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC4NCkpMRiBHUiBWZXIgMS4wDQoAOw==';
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Graphing
+ //
+ ///////////////////////////////////////////////////////////////////////////////
+
+ // add dashed lines!
+ // from http://davidowens.wordpress.com/2010/09/07/html-5-canvas-and-dashed-lines/
+ CanvasRenderingContext2D.prototype.dashedLineTo = function(fromX, fromY, toX, toY, pattern) {
+ // Our growth rate for our line can be one of the following:
+ // (+,+), (+,-), (-,+), (-,-)
+ // Because of this, our algorithm needs to understand if the x-coord and
+ // y-coord should be getting smaller or larger and properly cap the values
+ // based on (x,y).
+ var lt = function (a, b) { return a <= b; };
+ var gt = function (a, b) { return a >= b; };
+ var capmin = function (a, b) { return Math.min(a, b); };
+ var capmax = function (a, b) { return Math.max(a, b); };
+
+ var checkX = { thereYet: gt, cap: capmin };
+ var checkY = { thereYet: gt, cap: capmin };
+
+ if (fromY - toY > 0) {
+ checkY.thereYet = lt;
+ checkY.cap = capmax;
+ }
+ if (fromX - toX > 0) {
+ checkX.thereYet = lt;
+ checkX.cap = capmax;
+ }
+
+ this.moveTo(fromX, fromY);
+ var offsetX = fromX;
+ var offsetY = fromY;
+ var idx = 0, dash = true;
+ while (!(checkX.thereYet(offsetX, toX) && checkY.thereYet(offsetY, toY))) {
+ var ang = Math.atan2(toY - fromY, toX - fromX);
+ var len = pattern[idx];
+
+ offsetX = checkX.cap(toX, offsetX + (Math.cos(ang) * len));
+ offsetY = checkY.cap(toY, offsetY + (Math.sin(ang) * len));
+
+ if (dash) this.lineTo(offsetX, offsetY);
+ else this.moveTo(offsetX, offsetY);
+
+ idx = (idx + 1) % pattern.length;
+ dash = !dash;
+ }
+ };
+
+ // given a range of values, return a new range [vmin',vmax'] where the limits
+ // have been chosen "nicely". Taken from matplotlib.ticker.LinearLocator
+ function view_limits(vmin,vmax) {
+ // deal with degenerate case...
+ if (vmin == vmax) {
+ if (vmin == 0) { vmin = -0.5; vmax = 0.5; }
+ else {
+ vmin = vmin > 0 ? 0.9*vmin : 1.1*vmin;
+ vmax = vmax > 0 ? 1.1*vmax : 0.9*vmax;
+ }
+ }
+
+ var log_range = Math.log(vmax - vmin)/Math.LN10;
+ var exponent = Math.floor(log_range);
+ //if (log_range - exponent < 0.5) exponent -= 1;
+ var scale = Math.pow(10,-exponent);
+ vmin = Math.floor(scale*vmin)/scale;
+ vmax = Math.ceil(scale*vmax)/scale;
+
+ return [vmin,vmax,1.0/scale];
+ }
+
+ function engineering_notation(n,nplaces,trim) {
+ if (n == 0) return '0';
+ if (n == undefined) return 'undefined';
+ if (trim == undefined) trim = true;
+
+ var sign = n < 0 ? -1 : 1;
+ var log10 = Math.log(sign*n)/Math.LN10;
+ var exp = Math.floor(log10/3); // powers of 1000
+ var mantissa = sign*Math.pow(10,log10 - 3*exp);
+
+ // keep specified number of places following decimal point
+ var mstring = (mantissa + sign*0.5*Math.pow(10,-nplaces)).toString();
+ var mlen = mstring.length;
+ var endindex = mstring.indexOf('.');
+ if (endindex != -1) {
+ if (nplaces > 0) {
+ endindex += nplaces + 1;
+ if (endindex > mlen) endindex = mlen;
+ if (trim) {
+ while (mstring.charAt(endindex-1) == '0') endindex -= 1;
+ if (mstring.charAt(endindex-1) == '.') endindex -= 1;
+ }
+ }
+ if (endindex < mlen)
+ mstring = mstring.substring(0,endindex);
+ }
+
+ switch(exp) {
+ case -5: return mstring+"f";
+ case -4: return mstring+"p";
+ case -3: return mstring+"n";
+ case -2: return mstring+"u";
+ case -1: return mstring+"m";
+ case 0: return mstring;
+ case 1: return mstring+"K";
+ case 2: return mstring+"M";
+ case 3: return mstring+"G";
+ }
+
+ // don't have a good suffix, so just print the number
+ return n.toString();
+ }
+
+ var grid_pattern = [1,2];
+ var cursor_pattern = [5,5];
+
+ // x_values is an array of x coordinates for each of the plots
+ // y_values is an array of [color, value_array], one entry for each plot on left vertical axis
+ // z_values is an array of [color, value_array], one entry for each plot on right vertical axis
+ Schematic.prototype.graph = function(x_values,x_legend,y_values,y_legend,z_values,z_legend) {
+ var pwidth = 400; // dimensions of actual plot
+ var pheight = 300; // dimensions of actual plot
+ var left_margin = (y_values != undefined && y_values.length > 0) ? 55 : 25;
+ var top_margin = 25;
+ var right_margin = (z_values != undefined && z_values.length > 0) ? 55 : 25;
+ var bottom_margin = 45;
+ var tick_length = 5;
+
+ var w = pwidth + left_margin + right_margin;
+ var h = pheight + top_margin + bottom_margin;
+
+ var canvas = document.createElement('canvas');
+ canvas.width = w;
+ canvas.height = h;
+
+ // the graph itself will be drawn here and this image will be copied
+ // onto canvas, where it can be overlayed with mouse cursors, etc.
+ var bg_image = document.createElement('canvas');
+ bg_image.width = w;
+ bg_image.height = h;
+ canvas.bg_image = bg_image; // so we can find it during event handling
+
+ // start by painting an opaque background
+ var c = bg_image.getContext('2d');
+ c.fillStyle = background_style;
+ c.fillRect(0,0,w,h);
+ c.fillStyle = element_style;
+ c.fillRect(left_margin,top_margin,pwidth,pheight);
+
+ // figure out scaling for plots
+ var x_min = array_min(x_values);
+ var x_max = array_max(x_values);
+ var x_limits = view_limits(x_min,x_max);
+ x_min = x_limits[0];
+ x_max = x_limits[1];
+ var x_scale = pwidth/(x_max - x_min);
+
+ function plot_x(x) {
+ return (x - x_min)*x_scale + left_margin;
+ }
+
+ // draw x grid
+ c.strokeStyle = grid_style;
+ c.lineWidth = 1;
+ c.fillStyle = normal_style;
+ c.font = '10pt sans-serif';
+ c.textAlign = 'center';
+ c.textBaseline = 'top';
+ var end = top_margin + pheight;
+ for (var x = x_min; x <= x_max; x += x_limits[2]) {
+ var temp = plot_x(x) + 0.5; // keep lines crisp!
+
+ // grid line
+ c.beginPath();
+ if (x == x_min) {
+ c.moveTo(temp,top_margin);
+ c.lineTo(temp,end);
+ } else
+ c.dashedLineTo(temp,top_margin,temp,end,grid_pattern);
+ c.stroke();
+
+ // tick mark
+ c.beginPath();
+ c.moveTo(temp,end);
+ c.lineTo(temp,end + tick_length);
+ c.stroke();
+ c.fillText(engineering_notation(x,2),temp,end + tick_length);
+ }
+
+ if (y_values != undefined && y_values.length > 0) {
+ var y_min = Infinity;
+ var y_max = -Infinity;
+ var plot;
+ for (plot = y_values.length - 1; plot >= 0; --plot) {
+ var values = y_values[plot][2];
+ if (values == undefined) continue; // no data points
+ var offset = y_values[plot][1];
+ var temp = array_min(values) + offset;
+ if (temp < y_min) y_min = temp;
+ temp = array_max(values) + offset;
+ if (temp > y_max) y_max = temp;
+ }
+ var y_limits = view_limits(y_min,y_max);
+ y_min = y_limits[0];
+ y_max = y_limits[1];
+ var y_scale = pheight/(y_max - y_min);
+
+ function plot_y(y) {
+ return (y_max - y)*y_scale + top_margin;
+ }
+
+ // draw y grid
+ c.textAlign = 'right';
+ c.textBaseline = 'middle';
+ for (var y = y_min; y <= y_max; y += y_limits[2]) {
+ if (Math.abs(y/y_max) < 0.001) y = 0.0; // Just 3 digits
+ var temp = plot_y(y) + 0.5; // keep lines crisp!
+
+ // grid line
+ c.beginPath();
+ if (y == y_min) {
+ c.moveTo(left_margin,temp);
+ c.lineTo(left_margin + pwidth,temp);
+ } else
+ c.dashedLineTo(left_margin,temp,left_margin + pwidth,temp,grid_pattern);
+ c.stroke();
+
+ // tick mark
+ c.beginPath();
+ c.moveTo(left_margin - tick_length,temp);
+ c.lineTo(left_margin,temp);
+ c.stroke();
+ c.fillText(engineering_notation(y,2),left_margin - tick_length -2,temp);
+ }
+
+ // now draw each plot
+ var x,y;
+ var nx,ny;
+ c.lineWidth = 3;
+ c.lineCap = 'round';
+ for (plot = y_values.length - 1; plot >= 0; --plot) {
+ var color = probe_colors_rgb[y_values[plot][0]];
+ if (color == undefined) continue; // no plot color (== x-axis)
+ c.strokeStyle = color;
+ var values = y_values[plot][2];
+ if (values == undefined) continue; // no data points
+ var offset = y_values[plot][1];
+
+ x = plot_x(x_values[0]);
+ y = plot_y(values[0] + offset);
+ c.beginPath();
+ c.moveTo(x,y);
+ for (var i = 1; i < x_values.length; i++) {
+ nx = plot_x(x_values[i]);
+ ny = plot_y(values[i] + offset);
+ c.lineTo(nx,ny);
+ x = nx;
+ y = ny;
+ if (i % 100 == 99) {
+ // too many lineTo's cause canvas to break
+ c.stroke();
+ c.beginPath();
+ c.moveTo(x,y);
+ }
+ }
+ c.stroke();
+ }
+ }
+
+ if (z_values != undefined && z_values.length > 0) {
+ var z_min = Infinity;
+ var z_max = -Infinity;
+ for (plot = z_values.length - 1; plot >= 0; --plot) {
+ var values = z_values[plot][2];
+ if (values == undefined) continue; // no data points
+ var offset = z_values[plot][1];
+ var temp = array_min(values) + offset;
+ if (temp < z_min) z_min = temp;
+ temp = array_max(values) + offset;
+ if (temp > z_max) z_max = temp;
+ }
+ var z_limits = view_limits(z_min,z_max);
+ z_min = z_limits[0];
+ z_max = z_limits[1];
+ var z_scale = pheight/(z_max - z_min);
+
+ function plot_z(z) {
+ return (z_max - z)*z_scale + top_margin;
+ }
+
+ // draw z ticks
+ c.textAlign = 'left';
+ c.textBaseline = 'middle';
+ c.lineWidth = 1;
+ c.strokeStyle = normal_style;
+ var tick_length_half = Math.floor(tick_length/2);
+ var tick_delta = tick_length - tick_length_half;
+ for (var z = z_min; z <= z_max; z += z_limits[2]) {
+ if (Math.abs(z/z_max) < 0.001) z = 0.0; // Just 3 digits
+ var temp = plot_z(z) + 0.5; // keep lines crisp!
+
+ // tick mark
+ c.beginPath();
+ c.moveTo(left_margin + pwidth - tick_length_half,temp);
+ c.lineTo(left_margin + pwidth + tick_delta,temp);
+ c.stroke();
+ c.fillText(engineering_notation(z,2),left_margin + pwidth + tick_length + 2,temp);
+ }
+
+ var z;
+ var nz;
+ c.lineWidth = 3;
+ for (plot = z_values.length - 1; plot >= 0; --plot) {
+ var color = probe_colors_rgb[z_values[plot][0]];
+ if (color == undefined) continue; // no plot color (== x-axis)
+ c.strokeStyle = color;
+ var values = z_values[plot][2];
+ if (values == undefined) continue; // no data points
+ var offset = z_values[plot][1];
+
+ x = plot_x(x_values[0]);
+ z = plot_z(values[0] + offset);
+ c.beginPath();
+ c.moveTo(x,z);
+ for (var i = 1; i < x_values.length; i++) {
+ nx = plot_x(x_values[i]);
+ nz = plot_z(values[i] + offset);
+ c.lineTo(nx,nz);
+ x = nx;
+ z = nz;
+ if (i % 100 == 99) {
+ // too many lineTo's cause canvas to break
+ c.stroke();
+ c.beginPath();
+ c.moveTo(x,z);
+ }
+ }
+ c.stroke();
+ }
+ }
+
+ // draw legends
+ c.font = '12pt sans-serif';
+ c.textAlign = 'center';
+ c.textBaseline = 'bottom';
+ c.fillText(x_legend,left_margin + pwidth/2,h - 5);
+
+ if (y_values != undefined && y_values.length > 0) {
+ c.textBaseline = 'top';
+ c.save();
+ c.translate(5 ,top_margin + pheight/2);
+ c.rotate(-Math.PI/2);
+ c.fillText(y_legend,0,0);
+ c.restore();
+ }
+
+ if (z_values != undefined && z_values.length > 0) {
+ c.textBaseline = 'bottom';
+ c.save();
+ c.translate(w-5 ,top_margin + pheight/2);
+ c.rotate(-Math.PI/2);
+ c.fillText(z_legend,0,0);
+ c.restore();
+ }
+
+ // save info need for interactions with the graph
+ canvas.x_values = x_values;
+ canvas.y_values = y_values;
+ canvas.z_values = z_values;
+ canvas.x_legend = x_legend;
+ canvas.y_legend = y_legend;
+ canvas.z_legend = y_legend;
+ canvas.x_min = x_min;
+ canvas.x_scale = x_scale;
+ canvas.y_min = y_min;
+ canvas.y_scale = y_scale;
+ canvas.z_min = z_min;
+ canvas.z_scale = z_scale;
+ canvas.left_margin = left_margin;
+ canvas.top_margin = top_margin;
+ canvas.pwidth = pwidth;
+ canvas.pheight = pheight;
+ canvas.tick_length = tick_length;
+
+ canvas.cursor1_x = undefined;
+ canvas.cursor2_x = undefined;
+ canvas.sch = this;
+
+ // do something useful when user mouses over graph
+ canvas.addEventListener('mousemove',graph_mouse_move,false);
+
+ // return our masterpiece
+ redraw_plot(canvas);
+ return canvas;
+ }
+
+ function array_max(a) {
+ max = -Infinity;
+ for (var i = a.length - 1; i >= 0; --i)
+ if (a[i] > max) max = a[i];
+ return max;
+ }
+
+ function array_min(a) {
+ min = Infinity;
+ for (var i = a.length - 1; i >= 0; --i)
+ if (a[i] < min) min = a[i];
+ return min;
+ }
+
+ function plot_cursor(c,graph,cursor_x,left_margin) {
+ // draw dashed vertical marker that follows mouse
+ var x = graph.left_margin + cursor_x;
+ var end_y = graph.top_margin + graph.pheight + graph.tick_length;
+ c.strokeStyle = grid_style;
+ c.lineWidth = 1;
+ c.beginPath();
+ c.dashedLineTo(x,graph.top_margin,x,end_y,cursor_pattern);
+ c.stroke();
+
+ // add x label at bottom of marker
+ var graph_x = cursor_x/graph.x_scale + graph.x_min;
+ c.font = '10pt sans-serif';
+ c.textAlign = 'center';
+ c.textBaseline = 'top';
+ c.fillStyle = background_style;
+ c.fillText('\u2588\u2588\u2588\u2588\u2588',x,end_y);
+ c.fillStyle = normal_style;
+ c.fillText(engineering_notation(graph_x,3,false),x,end_y);
+
+ // compute which points marker is between
+ var x_values = graph.x_values;
+ var len = x_values.length;
+ var index = 0;
+ while (index < len && graph_x >= x_values[index]) index += 1;
+ var x1 = (index == 0) ? x_values[0] : x_values[index-1];
+ var x2 = x_values[index];
+
+ if (x2 != undefined) {
+ // for each plot, interpolate and output value at intersection with marker
+ c.textAlign = 'left';
+ var tx = graph.left_margin + left_margin;
+ var ty = graph.top_margin;
+ if (graph.y_values != undefined) {
+ for (var plot = 0; plot < graph.y_values.length; plot++) {
+ var values = graph.y_values[plot][2];
+ var color = probe_colors_rgb[graph.y_values[plot][0]];
+ if (values == undefined || color == undefined) continue; // no data points or x-axis
+
+ // interpolate signal value at graph_x using values[index-1] and values[index]
+ var y1 = (index == 0) ? values[0] : values[index-1];
+ var y2 = values[index];
+ var y = y1;
+ if (graph_x != x1) y += (graph_x - x1)*(y2 - y1)/(x2 - x1);
+
+ // annotate plot with value of signal at marker
+ c.fillStyle = element_style;
+ c.fillText('\u2588\u2588\u2588\u2588\u2588',tx-3,ty);
+ c.fillStyle = color;
+ c.fillText(engineering_notation(y,3,false),tx,ty);
+ ty += 14;
+ }
+ }
+
+ c.textAlign = 'right';
+ if (graph.z_values != undefined) {
+ var tx = graph.left_margin + graph.pwidth - left_margin;
+ var ty = graph.top_margin;
+ for (var plot = 0; plot < graph.z_values.length; plot++) {
+ var values = graph.z_values[plot][2];
+ var color = probe_colors_rgb[graph.z_values[plot][0]];
+ if (values == undefined || color == undefined) continue; // no data points or x-axis
+
+ // interpolate signal value at graph_x using values[index-1] and values[index]
+ var z1 = (index == 0) ? values[0]: values[index-1];
+ var z2 = values[index];
+ var z = z1;
+ if (graph_x != x1) z += (graph_x - x1)*(z2 - z1)/(x2 - x1);
+
+ // annotate plot with value of signal at marker
+ c.fillStyle = element_style;
+ c.fillText('\u2588\u2588\u2588\u2588\u2588',tx+3,ty);
+ c.fillStyle = color;
+ c.fillText(engineering_notation(z,3,false),tx,ty);
+ ty += 14;
+ }
+ }
+ }
+ }
+
+ function redraw_plot(graph) {
+ var c = graph.getContext('2d');
+ c.drawImage(graph.bg_image,0,0);
+
+ if (graph.cursor1_x != undefined) plot_cursor(c,graph,graph.cursor1_x,4);
+ if (graph.cursor2_x != undefined) plot_cursor(c,graph,graph.cursor2_x,30);
+
+ /*
+ if (graph.cursor1_x != undefined) {
+ // draw dashed vertical marker that follows mouse
+ var x = graph.left_margin + graph.cursor1_x;
+ var end_y = graph.top_margin + graph.pheight + graph.tick_length;
+ c.strokeStyle = grid_style;
+ c.lineWidth = 1;
+ c.beginPath();
+ c.dashedLineTo(x,graph.top_margin,x,end_y,cursor_pattern);
+ c.stroke();
+
+ // add x label at bottom of marker
+ var graph_x = graph.cursor1_x/graph.x_scale + graph.x_min;
+ c.font = '10pt sans-serif';
+ c.textAlign = 'center';
+ c.textBaseline = 'top';
+ c.fillStyle = background_style;
+ c.fillText('\u2588\u2588\u2588\u2588\u2588',x,end_y);
+ c.fillStyle = normal_style;
+ c.fillText(engineering_notation(graph_x,3,false),x,end_y);
+
+ // compute which points marker is between
+ var x_values = graph.x_values;
+ var len = x_values.length;
+ var index = 0;
+ while (index < len && graph_x >= x_values[index]) index += 1;
+ var x1 = (index == 0) ? x_values[0] : x_values[index-1];
+ var x2 = x_values[index];
+
+ if (x2 != undefined) {
+ // for each plot, interpolate and output value at intersection with marker
+ c.textAlign = 'left';
+ var tx = graph.left_margin + 4;
+ var ty = graph.top_margin;
+ for (var plot = 0; plot < graph.y_values.length; plot++) {
+ var values = graph.y_values[plot][1];
+
+ // interpolate signal value at graph_x using values[index-1] and values[index]
+ var y1 = (index == 0) ? values[0] : values[index-1];
+ var y2 = values[index];
+ var y = y1;
+ if (graph_x != x1) y += (graph_x - x1)*(y2 - y1)/(x2 - x1);
+
+ // annotate plot with value of signal at marker
+ c.fillStyle = element_style;
+ c.fillText('\u2588\u2588\u2588\u2588\u2588',tx-3,ty);
+ c.fillStyle = probe_colors_rgb[graph.y_values[plot][0]];
+ c.fillText(engineering_notation(y,3,false),tx,ty);
+ ty += 14;
+ }
+ }
+ }
+ */
+ }
+
+ function graph_mouse_move(event) {
+ if (!event) event = window.event;
+ var g = (window.event) ? event.srcElement : event.target;
+
+ g.relMouseCoords(event);
+ // not sure yet where the 3,-3 offset correction comes from (borders? padding?)
+ var gx = g.mouse_x - g.left_margin - 3;
+ var gy = g.pheight - (g.mouse_y - g.top_margin) + 3;
+ if (gx >= 0 && gx <= g.pwidth && gy >=0 && gy <= g.pheight) {
+ //g.sch.message('button: '+event.button+', which: '+event.which);
+ g.cursor1_x = gx;
+ } else {
+ g.cursor1_x = undefined;
+ g.cursor2_x = undefined;
+ }
+
+ redraw_plot(g);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Parts bin
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ // one instance will be created for each part in the parts bin
+ function Part(sch) {
+ this.sch = sch;
+ this.component = undefined;
+ this.selected = false;
+
+ // set up canvas
+ this.canvas = document.createElement('canvas');
+ this.canvas.style.borderStyle = 'solid';
+ this.canvas.style.borderWidth = '1px';
+ this.canvas.style.borderColor = background_style;
+ //this.canvas.style.position = 'absolute';
+ this.canvas.style.cursor = 'default';
+ this.canvas.height = part_w;
+ this.canvas.width = part_h;
+ this.canvas.part = this;
+
+ this.canvas.addEventListener('mouseover',part_enter,false);
+ this.canvas.addEventListener('mouseout',part_leave,false);
+ this.canvas.addEventListener('mousedown',part_mouse_down,false);
+ this.canvas.addEventListener('mouseup',part_mouse_up,false);
+
+ // make the part "clickable" by registering a dummy click handler
+ // this should make things work on the iPad
+ this.canvas.addEventListener('click',function(){},false);
+ }
+
+ Part.prototype.set_location = function(left,top) {
+ this.canvas.style.left = left + 'px';
+ this.canvas.style.top = top + 'px';
+ }
+
+ Part.prototype.right = function() {
+ return this.canvas.offsetLeft + this.canvas.offsetWidth;
+ }
+
+ Part.prototype.bottom = function() {
+ return this.canvas.offsetTop + this.canvas.offsetHeight;
+ }
+
+ Part.prototype.set_component = function(component,tip) {
+ component.sch = this;
+ this.component = component;
+ this.tip = tip;
+
+ // figure out scaling and centering of parts icon
+ var b = component.bounding_box;
+ var dx = b[2] - b[0];
+ var dy = b[3] - b[1];
+ this.scale = 0.8; //Math.min(part_w/(1.2*dx),part_h/(1.2*dy));
+ this.origin_x = b[0] + dx/2.0 - part_w/(2.0*this.scale);
+ this.origin_y = b[1] + dy/2.0 - part_h/(2.0*this.scale);
+
+ this.redraw();
+ }
+
+ Part.prototype.redraw = function(part) {
+ var c = this.canvas.getContext('2d');
+
+ // paint background color
+ c.fillStyle = this.selected ? selected_style : background_style;
+ c.fillRect(0,0,part_w,part_h);
+
+ if (this.component) this.component.draw(c);
+ }
+
+ Part.prototype.select = function(which) {
+ this.selected = which;
+ this.redraw();
+ }
+
+ Part.prototype.update_connection_point = function(cp,old_location) {
+ // no connection points in the parts bin
+ }
+
+ Part.prototype.moveTo = function(c,x,y) {
+ c.moveTo((x - this.origin_x) * this.scale,(y - this.origin_y) * this.scale);
+ }
+
+ Part.prototype.lineTo = function(c,x,y) {
+ c.lineTo((x - this.origin_x) * this.scale,(y - this.origin_y) * this.scale);
+ }
+
+ Part.prototype.draw_line = function(c,x1,y1,x2,y2,width) {
+ c.lineWidth = width*this.scale;
+ c.beginPath();
+ c.moveTo((x1 - this.origin_x) * this.scale,(y1 - this.origin_y) * this.scale);
+ c.lineTo((x2 - this.origin_x) * this.scale,(y2 - this.origin_y) * this.scale);
+ c.stroke();
+ }
+
+ Part.prototype.draw_arc = function(c,x,y,radius,start_radians,end_radians,anticlockwise,width,filled) {
+ c.lineWidth = width*this.scale;
+ c.beginPath();
+ c.arc((x - this.origin_x)*this.scale,(y - this.origin_y)*this.scale,radius*this.scale,
+ start_radians,end_radians,anticlockwise);
+ if (filled) c.fill();
+ else c.stroke();
+ }
+
+ Part.prototype.draw_text = function(c,text,x,y,size) {
+ // no text displayed for the parts icon
+ }
+
+ function part_enter(event) {
+ if (!event) event = window.event;
+ var canvas = (window.event) ? event.srcElement : event.target;
+ var part = canvas.part;
+
+ // avoid Chrome bug that changes to text cursor whenever
+ // drag starts. We'll restore the default handler at
+ // the appropriate point so behavior in other parts of
+ // the document are unaffected.
+ //part.sch.saved_onselectstart = document.onselectstart;
+ //document.onselectstart = function () { return false; };
+
+ canvas.style.borderColor = normal_style;
+ part.sch.message(part.tip+': drag onto diagram to insert');
+ return false;
+ }
+
+ function part_leave(event) {
+ if (!event) event = window.event;
+ var canvas = (window.event) ? event.srcElement : event.target;
+ var part = canvas.part;
+
+ if (typeof part.sch.new_part == 'undefined') {
+ // leaving with no part selected? revert handler
+ //document.onselectstart = part.sch.saved_onselectstart;
+ }
+
+ canvas.style.borderColor = background_style;
+ part.sch.message('');
+ return false;
+ }
+
+ function part_mouse_down(event) {
+ if (!event) event = window.event;
+ var part = (window.event) ? event.srcElement.part : event.target.part;
+
+ part.select(true);
+ part.sch.new_part = part;
+ return false;
+ }
+
+ function part_mouse_up(event) {
+ if (!event) event = window.event;
+ var part = (window.event) ? event.srcElement.part : event.target.part;
+
+ part.select(false);
+ part.sch.new_part = undefined;
+ return false;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Rectangle helper functions
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ // rect is an array of the form [left,top,right,bottom]
+
+ // ensure left < right, top < bottom
+ function canonicalize(r) {
+ var temp;
+
+ // canonicalize bounding box
+ if (r[0] > r[2]) {
+ temp = r[0];
+ r[0] = r[2];
+ r[2] = temp;
+ }
+ if (r[1] > r[3]) {
+ temp = r[1];
+ r[1] = r[3];
+ r[3] = temp;
+ }
+ }
+
+ function between(x,x1,x2) {
+ return x1 <= x && x <= x2;
+ }
+
+ function inside(rect,x,y) {
+ return between(x,rect[0],rect[2]) && between(y,rect[1],rect[3]);
+ }
+
+ // only works for manhattan rectangles
+ function intersect(r1,r2) {
+ // look for non-intersection, negate result
+ var result = !(r2[0] > r1[2] ||
+ r2[2] < r1[0] ||
+ r2[1] > r1[3] ||
+ r2[3] < r1[1]);
+
+ // if I try to return the above expression, javascript returns undefined!!!
+ return result;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Component base class
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function Component(type,x,y,rotation) {
+ this.sch = undefined;
+ this.type = type;
+ this.x = x;
+ this.y = y;
+ this.rotation = rotation;
+ this.selected = false;
+ this.properties = new Array();
+ this.bounding_box = [0,0,0,0]; // in device coords [left,top,right,bottom]
+ this.bbox = this.bounding_box; // in absolute coords
+ this.connections = [];
+ }
+
+ Component.prototype.json = function(index) {
+ this.properties['_json_'] = index; // remember where we are in the JSON list
+
+ var props = {};
+ for (var p in this.properties) props[p] = this.properties[p];
+
+ var conns = [];
+ for (var i = 0; i < this.connections.length; i++)
+ conns.push(this.connections[i].json());
+
+ var json = [this.type,[this.x, this.y, this.rotation],props,conns];
+ return json;
+ }
+
+ Component.prototype.add_connection = function(offset_x,offset_y) {
+ this.connections.push(new ConnectionPoint(this,offset_x,offset_y));
+ }
+
+ Component.prototype.update_coords = function() {
+ var x = this.x;
+ var y = this.y;
+
+ // update bbox
+ var b = this.bounding_box;
+ this.bbox[0] = this.transform_x(b[0],b[1]) + x;
+ this.bbox[1] = this.transform_y(b[0],b[1]) + y;
+ this.bbox[2] = this.transform_x(b[2],b[3]) + x;
+ this.bbox[3] = this.transform_y(b[2],b[3]) + y;
+ canonicalize(this.bbox);
+
+ // update connections
+ for (var i = this.connections.length - 1; i >= 0; --i)
+ this.connections[i].update_location();
+ }
+
+ Component.prototype.rotate = function(amount) {
+ var old_rotation = this.rotation;
+ this.rotation = (this.rotation + amount) % 8;
+ this.update_coords();
+
+ // create an undoable edit record here
+ // using old_rotation
+ }
+
+ Component.prototype.move_begin = function() {
+ // remember where we started this move
+ this.move_x = this.x;
+ this.move_y = this.y;
+ }
+
+ Component.prototype.move = function(dx,dy) {
+ // update coordinates
+ this.x += dx;
+ this.y += dy;
+ this.update_coords();
+ }
+
+ Component.prototype.move_end = function() {
+ var dx = this.x - this.move_x;
+ var dy = this.y - this.move_y;
+
+ if (dx != 0 || dy != 0) {
+ // create an undoable edit record here
+
+ this.sch.check_wires(this);
+ }
+ }
+
+ Component.prototype.add = function(sch) {
+ this.sch = sch; // we now belong to a schematic!
+ sch.add_component(this);
+ this.update_coords();
+ }
+
+ Component.prototype.remove = function() {
+ // remove connection points from schematic
+ for (var i = this.connections.length - 1; i >= 0; --i) {
+ var cp = this.connections[i];
+ this.sch.remove_connection_point(cp,cp.location);
+ }
+
+ // remove component from schematic
+ this.sch.remove_component(this);
+ this.sch = undefined;
+
+ // create an undoable edit record here
+ }
+
+ Component.prototype.transform_x = function(x,y) {
+ var rot = this.rotation;
+ if (rot == 0 || rot == 6) return x;
+ else if (rot == 1 || rot == 5) return -y;
+ else if (rot == 2 || rot == 4) return -x;
+ else return y;
+ }
+
+ Component.prototype.transform_y = function(x,y) {
+ var rot = this.rotation;
+ if (rot == 1 || rot == 7) return x;
+ else if (rot == 2 || rot == 6) return -y;
+ else if (rot == 3 || rot == 5) return -x;
+ else return y;
+ }
+
+ Component.prototype.moveTo = function(c,x,y) {
+ var nx = this.transform_x(x,y) + this.x;
+ var ny = this.transform_y(x,y) + this.y;
+ this.sch.moveTo(c,nx,ny);
+ }
+
+ Component.prototype.lineTo = function(c,x,y) {
+ var nx = this.transform_x(x,y) + this.x;
+ var ny = this.transform_y(x,y) + this.y;
+ this.sch.lineTo(c,nx,ny);
+ }
+
+ Component.prototype.draw_line = function(c,x1,y1,x2,y2) {
+ c.strokeStyle = this.selected ? selected_style :
+ this.type == 'w' ? normal_style : component_style;
+ var nx1 = this.transform_x(x1,y1) + this.x;
+ var ny1 = this.transform_y(x1,y1) + this.y;
+ var nx2 = this.transform_x(x2,y2) + this.x;
+ var ny2 = this.transform_y(x2,y2) + this.y;
+ this.sch.draw_line(c,nx1,ny1,nx2,ny2,1);
+ }
+
+ Component.prototype.draw_circle = function(c,x,y,radius,filled) {
+ if (filled) c.fillStyle = this.selected ? selected_style : normal_style;
+ else c.strokeStyle = this.selected ? selected_style :
+ this.type == 'w' ? normal_style : component_style;
+ var nx = this.transform_x(x,y) + this.x;
+ var ny = this.transform_y(x,y) + this.y;
+
+ this.sch.draw_arc(c,nx,ny,radius,0,2*Math.PI,false,1,filled);
+ }
+
+ rot_angle = [
+ 0.0, // NORTH (identity)
+ Math.PI/2, // EAST (rot270)
+ Math.PI, // SOUTH (rot180)
+ 3*Math.PI/2, // WEST (rot90)
+ 0.0, // RNORTH (negy)
+ Math.PI/2, // REAST (int-neg)
+ Math.PI, // RSOUTH (negx)
+ 3*Math.PI/2, // RWEST (int-pos)
+ ];
+
+ Component.prototype.draw_arc = function(c,x,y,radius,start_radians,end_radians) {
+ c.strokeStyle = this.selected ? selected_style :
+ this.type == 'w' ? normal_style : component_style;
+ var nx = this.transform_x(x,y) + this.x;
+ var ny = this.transform_y(x,y) + this.y;
+ this.sch.draw_arc(c,nx,ny,radius,
+ start_radians+rot_angle[this.rotation],end_radians+rot_angle[this.rotation],
+ false,1,false);
+ }
+
+ Component.prototype.draw = function(c) {
+ /*
+ for (var i = this.connections.length - 1; i >= 0; --i) {
+ var cp = this.connections[i];
+ cp.draw_x(c);
+ }
+ */
+ }
+
+ // result of rotating an alignment [rot*9 + align]
+ aOrient = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, // NORTH (identity)
+ 2, 5, 8, 1, 4, 7, 0, 3, 6, // EAST (rot270)
+ 8, 7, 6, 5, 4, 3, 2, 1, 0, // SOUTH (rot180)
+ 6, 3, 0, 7, 4, 1, 8, 5, 3, // WEST (rot90)
+ 2, 1, 0, 5, 4, 3, 8, 7, 6, // RNORTH (negy)
+ 8, 5, 2, 7, 4, 1, 6, 3, 0, // REAST (int-neg)
+ 6, 7, 8, 3, 4, 5, 0, 1, 2, // RSOUTH (negx)
+ 0, 3, 6, 1, 4, 7, 2, 5, 8 // RWEST (int-pos)
+ ];
+
+ textAlign = [
+ 'left', 'center', 'right',
+ 'left', 'center', 'right',
+ 'left', 'center', 'right'
+ ];
+
+ textBaseline = [
+ 'top', 'top', 'top',
+ 'middle', 'middle', 'middle',
+ 'bottom', 'bottom', 'bottom'
+ ];
+
+ Component.prototype.draw_text = function(c,text,x,y,alignment,size,fill) {
+ var a = aOrient[this.rotation*9 + alignment];
+ c.textAlign = textAlign[a];
+ c.textBaseline = textBaseline[a];
+ if (fill == undefined)
+ c.fillStyle = this.selected ? selected_style : normal_style;
+ else
+ c.fillStyle = fill;
+ this.sch.draw_text(c,text,
+ this.transform_x(x,y) + this.x,
+ this.transform_y(x,y) + this.y,
+ size);
+ }
+
+ Component.prototype.set_select = function(which) {
+ if (which != this.selected) {
+ this.selected = which;
+ // create an undoable edit record here
+ }
+ }
+
+ Component.prototype.select = function(x,y,shiftKey) {
+ this.was_previously_selected = this.selected;
+ if (this.near(x,y)) {
+ this.set_select(shiftKey ? !this.selected : true);
+ return true;
+ } else return false;
+ }
+
+ Component.prototype.select_rect = function(s) {
+ this.was_previously_selected = this.selected;
+ if (intersect(this.bbox,s))
+ this.set_select(true);
+ }
+
+ // if connection point of component c bisects the
+ // wire represented by this compononent, return that
+ // connection point. Otherwise return null.
+ Component.prototype.bisect = function(c) {
+ return null;
+ }
+
+ // does mouse click fall on this component?
+ Component.prototype.near = function(x,y) {
+ return inside(this.bbox,x,y);
+ }
+
+ Component.prototype.edit_properties = function(x,y) {
+ if (this.near(x,y)) {
+ // make an
widget for each property
+ var fields = new Array();
+ for (var i in this.properties)
+ // underscore at beginning of property name => system property
+ if (i.charAt(0) != '_')
+ fields[i] = build_input('text',10,this.properties[i]);
+
+ var content = build_table(fields);
+ content.fields = fields;
+ content.component = this;
+
+ this.sch.dialog('Edit Properties',content,function(content) {
+ for (var i in content.fields)
+ content.component.properties[i] = content.fields[i].value;
+ content.component.sch.redraw_background();
+ });
+ return true;
+ } else return false;
+ }
+
+ // clear the labels on all connections
+ Component.prototype.clear_labels = function() {
+ for (var i = this.connections.length - 1; i >=0; --i) {
+ this.connections[i].clear_label();
+ }
+ }
+
+ // default action: don't propagate label
+ Component.prototype.propagate_label = function(label) {
+ }
+
+ // give components a chance to generate default labels for their connection(s)
+ // default action: do nothing
+ Component.prototype.add_default_labels = function() {
+ }
+
+ // component should generate labels for all unlabeled connections
+ Component.prototype.label_connections = function() {
+ for (var i = this.connections.length - 1; i >=0; --i) {
+ var cp = this.connections[i];
+ if (!cp.label)
+ cp.propagate_label(this.sch.get_next_label());
+ }
+ }
+
+ // default behavior: no probe info
+ Component.prototype.probe_info = function() { return undefined; }
+
+ // default behavior: nothing to display for DC analysis
+ Component.prototype.display_current = function(c,vmap) {
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Connection point
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ connection_point_radius = 2;
+
+ function ConnectionPoint(parent,x,y) {
+ this.parent = parent;
+ this.offset_x = x;
+ this.offset_y = y;
+ this.location = '';
+ this.update_location();
+ this.label = undefined;
+ }
+
+ ConnectionPoint.prototype.toString = function() {
+ return '
';
+ }
+
+ ConnectionPoint.prototype.json = function() {
+ return this.label;
+ }
+
+ ConnectionPoint.prototype.clear_label = function() {
+ this.label = undefined;
+ }
+
+ ConnectionPoint.prototype.propagate_label = function(label) {
+ // should we check if existing label is the same? it should be...
+
+ if (this.label === undefined) {
+ // label this connection point
+ this.label = label;
+
+ // propagate label to coincident connection points
+ this.parent.sch.propagate_label(label,this.location);
+
+ // possibly label other cp's for this device?
+ this.parent.propagate_label(label);
+ } else if (this.label != '0' && label != '0' && this.label != label)
+ alert("Node has two conflicting labels: "+this.label+", "+label);
+ }
+
+ ConnectionPoint.prototype.update_location = function() {
+ // update location string which we use as a key to find coincident connection points
+ var old_location = this.location;
+ var parent = this.parent;
+ var nx = parent.transform_x(this.offset_x,this.offset_y) + parent.x;
+ var ny = parent.transform_y(this.offset_x,this.offset_y) + parent.y;
+ this.x = nx;
+ this.y = ny;
+ this.location = nx + ',' + ny;
+
+ // add ourselves to the connection list for the new location
+ if (parent.sch)
+ parent.sch.update_connection_point(this,old_location);
+ }
+
+ ConnectionPoint.prototype.coincident = function(x,y) {
+ return this.x==x && this.y==y;
+ }
+
+ ConnectionPoint.prototype.draw = function(c,n) {
+ if (n != 2)
+ this.parent.draw_circle(c,this.offset_x,this.offset_y,connection_point_radius,n > 2);
+ }
+
+ ConnectionPoint.prototype.draw_x = function(c) {
+ this.parent.draw_line(c,this.offset_x-2,this.offset_y-2,this.offset_x+2,this.offset_y+2,grid_style);
+ this.parent.draw_line(c,this.offset_x+2,this.offset_y-2,this.offset_x-2,this.offset_y+2,grid_style);
+ }
+
+ ConnectionPoint.prototype.display_voltage = function(c,vmap) {
+ var v = vmap[this.label];
+ if (v != undefined) {
+ var label = v.toFixed(2) + 'V';
+
+ // first draw some solid blocks in the background
+ c.globalAlpha = 0.85;
+ this.parent.draw_text(c,'\u2588\u2588\u2588',this.offset_x,this.offset_y,
+ 4,annotation_size,element_style);
+ c.globalAlpha = 1.0;
+
+ // display the node voltage at this connection point
+ this.parent.draw_text(c,label,this.offset_x,this.offset_y,
+ 4,annotation_size,annotation_style);
+
+ // only display each node voltage once
+ delete vmap[this.label];
+ }
+ }
+
+ // see if three connection points are collinear
+ function collinear(p1,p2,p3) {
+ // from http://mathworld.wolfram.com/Collinear.html
+ var area = p1.x*(p2.y - p3.y) + p2.x*(p3.y - p1.y) + p3.x*(p1.y - p2.y);
+ return area == 0;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Wire
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ near_distance = 2; // how close to wire counts as "near by"
+
+ function Wire(x1,y1,x2,y2) {
+ // arbitrarily call x1,y1 the origin
+ Component.call(this,'w',x1,y1,0);
+ this.dx = x2 - x1;
+ this.dy = y2 - y1;
+ this.add_connection(0,0);
+ this.add_connection(this.dx,this.dy);
+
+ // compute bounding box (expanded slightly)
+ var r = [0,0,this.dx,this.dy];
+ canonicalize(r);
+ r[0] -= near_distance;
+ r[1] -= near_distance;
+ r[2] += near_distance;
+ r[3] += near_distance;
+ this.bounding_box = r;
+ this.update_coords(); // update bbox
+
+ // used in selection calculations
+ this.len = Math.sqrt(this.dx*this.dx + this.dy*this.dy);
+ }
+ Wire.prototype = new Component();
+ Wire.prototype.constructor = Wire;
+
+ Wire.prototype.toString = function() {
+ return '';
+ }
+
+ // return connection point at other end of wire from specified cp
+ Wire.prototype.other_end = function(cp) {
+ if (cp == this.connections[0]) return this.connections[1];
+ else if (cp == this.connections[1]) return this.connections[0];
+ else return undefined;
+ }
+
+ Wire.prototype.json = function(index) {
+ var json = ['w',[this.x, this.y, this.x+this.dx, this.y+this.dy]];
+ return json;
+ }
+
+ Wire.prototype.draw = function(c) {
+ this.draw_line(c,0,0,this.dx,this.dy);
+ }
+
+ Wire.prototype.clone = function(x,y) {
+ return new Wire(x,y,x+this.dx,y+this.dy);
+ }
+
+ Wire.prototype.near = function(x,y) {
+ // crude check: (x,y) within expanded bounding box of wire
+ if (inside(this.bbox,x,y)) {
+ // compute distance between x,y and nearst point on line
+ // http://www.allegro.cc/forums/thread/589720
+ var D = Math.abs((x - this.x)*this.dy - (y - this.y)*this.dx)/this.len;
+ if (D <= near_distance) return true;
+ }
+ return false;
+ }
+
+ // selection rectangle selects wire only if it includes
+ // one of the end points
+ Wire.prototype.select_rect = function(s) {
+ this.was_previously_selected = this.selected;
+ if (inside(s,this.x,this.y) || inside(s,this.x+this.dx,this.y+this.dy))
+ this.set_select(true);
+ }
+
+ // if connection point cp bisects the
+ // wire represented by this compononent, return true
+ Wire.prototype.bisect_cp = function(cp) {
+ var x = cp.x;
+ var y = cp.y;
+
+ // crude check: (x,y) within expanded bounding box of wire
+ if (inside(this.bbox,x,y)) {
+ // compute distance between x,y and nearst point on line
+ // http://www.allegro.cc/forums/thread/589720
+ var D = Math.abs((x - this.x)*this.dy - (y - this.y)*this.dx)/this.len;
+ // final check: ensure point isn't an end point of the wire
+ if (D < 1 && !this.connections[0].coincident(x,y) && !this.connections[1].coincident(x,y))
+ return true;
+ }
+ return false;
+ }
+
+ // if some connection point of component c bisects the
+ // wire represented by this compononent, return that
+ // connection point. Otherwise return null.
+ Wire.prototype.bisect = function(c) {
+ if (c == undefined) return;
+ for (var i = c.connections.length - 1; i >= 0; --i) {
+ var cp = c.connections[i];
+ if (this.bisect_cp(cp)) return cp;
+ }
+ return null;
+ }
+
+ Wire.prototype.move_end = function() {
+ // look for wires bisected by this wire
+ this.sch.check_wires(this);
+
+ // look for connection points that might bisect us
+ this.sch.check_connection_points(this);
+ }
+
+ // wires "conduct" their label to the other end
+ Wire.prototype.propagate_label = function(label) {
+ // don't worry about relabeling a cp, it won't recurse!
+ this.connections[0].propagate_label(label);
+ this.connections[1].propagate_label(label);
+ }
+
+ // Wires have no properties to edit
+ Wire.prototype.edit_properties = function(x,y) {
+ return false;
+ }
+
+ // some actual component will start the labeling of electrical nodes,
+ // so do nothing here
+ Wire.prototype.label_connections = function() {
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Ground
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function Ground(x,y,rotation) {
+ Component.call(this,'g',x,y,rotation);
+ this.add_connection(0,0);
+ this.bounding_box = [-6,0,6,8];
+ this.update_coords();
+ }
+ Ground.prototype = new Component();
+ Ground.prototype.constructor = Ground;
+
+ Ground.prototype.toString = function() {
+ return '';
+ }
+
+ Ground.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,8);
+ this.draw_line(c,-6,8,6,8);
+ }
+
+ Ground.prototype.clone = function(x,y) {
+ return new Ground(x,y,this.rotation);
+ }
+
+ // Grounds no properties to edit
+ Ground.prototype.edit_properties = function(x,y) {
+ return false;
+ }
+
+ // give components a chance to generate a label for their connection(s)
+ // default action: do nothing
+ Ground.prototype.add_default_labels = function() {
+ this.connections[0].propagate_label('0'); // canonical label for GND node
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Label
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function Label(x,y,rotation,label) {
+ Component.call(this,'L',x,y,rotation);
+ this.properties['label'] = label ? label : '???';
+ this.add_connection(0,0);
+ this.bounding_box = [-2,0,2,8];
+ this.update_coords();
+ }
+ Label.prototype = new Component();
+ Label.prototype.constructor = Label;
+
+ Label.prototype.toString = function() {
+ return '';
+ }
+
+ Label.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,8);
+ this.draw_text(c,this.properties['label'],0,9,1,property_size);
+ }
+
+ Label.prototype.clone = function(x,y) {
+ return new Label(x,y,this.rotation,this.properties['label']);
+ }
+
+ // give components a chance to generate a label for their connection(s)
+ // default action: do nothing
+ Label.prototype.add_default_labels = function() {
+ this.connections[0].propagate_label(this.properties['label']);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Voltage Probe
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ probe_colors = ['red','green','blue','cyan','magenta','yellow','black','x-axis'];
+ probe_colors_rgb = {
+ 'red': 'rgb(255,64,64)',
+ 'green': 'rgb(64,255,64)',
+ 'blue': 'rgb(64,64,255)',
+ 'cyan': 'rgb(64,255,255)',
+ 'magenta' : 'rgb(255,64,255)',
+ 'yellow': 'rgb(255,255,64)',
+ 'black': 'rgb(0,0,0)',
+ 'x-axis': undefined,
+ };
+
+ function Probe(x,y,rotation,color,offset) {
+ Component.call(this,'s',x,y,rotation);
+ this.add_connection(0,0);
+ this.properties['color'] = color ? color : 'cyan';
+ this.properties['offset'] = (offset==undefined || offset=='') ? '0' : offset;
+ this.bounding_box = [0,0,27,-21];
+ this.update_coords();
+ }
+ Probe.prototype = new Component();
+ Probe.prototype.constructor = Probe;
+
+ Probe.prototype.toString = function() {
+ return '';
+ }
+
+ Probe.prototype.draw = function(c) {
+ // draw outline
+ this.draw_line(c,0,0,4,-4);
+ this.draw_line(c,2,-6,6,-2);
+ this.draw_line(c,2,-6,17,-21);
+ this.draw_line(c,6,-2,21,-17);
+ this.draw_line(c,17,-21,21,-17);
+ this.draw_arc(c,19,-11,8,3*Math.PI/2,0);
+
+ // fill body with plot color
+ var color = probe_colors_rgb[this.properties['color']];
+ if (color != undefined) {
+ c.fillStyle = color;
+ c.beginPath();
+ this.moveTo(c,2,-6);
+ this.lineTo(c,6,-2);
+ this.lineTo(c,21,-17);
+ this.lineTo(c,17,-21);
+ this.lineTo(c,2,-6);
+ c.fill();
+ } else {
+ this.draw_text(c,this.properties['color'],27,-11,1,property_size);
+ }
+ }
+
+ Probe.prototype.clone = function(x,y) {
+ return new Probe(x,y,this.rotation,this.properties['color'],this.properties['offset']);
+ }
+
+ Probe.prototype.edit_properties = function(x,y) {
+ if (inside(this.bbox,x,y)) {
+ var fields = new Array();
+ fields['Plot color'] = build_select(probe_colors,this.properties['color']);
+ fields['Plot offset'] = build_input('text',10,this.properties['offset']);
+
+ var content = build_table(fields);
+ content.fields = fields;
+ content.component = this;
+
+ this.sch.dialog('Edit Properties',content,function(content) {
+ var color_choice = content.fields['Plot color'];
+ content.component.properties['color'] = probe_colors[color_choice.selectedIndex];
+ content.component.properties['offset'] = content.fields['Plot offset'].value;
+ content.component.sch.redraw_background();
+ });
+ return true;
+ } else return false;
+ }
+
+ // return [color, node_label, offset, type] for this probe
+ Probe.prototype.probe_info = function() {
+ var color = this.properties['color'];
+ var offset = this.properties['offset'];
+ if (offset==undefined || offset=="") offset = '0';
+ return [color,this.connections[0].label,offset,'voltage'];
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Ammeter Probe
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function Ammeter(x,y,rotation,color,offset) {
+ Component.call(this,'a',x,y,rotation);
+ this.add_connection(0,0); // pos
+ this.add_connection(16,0); // neg
+ this.properties['color'] = color ? color : 'magenta';
+ this.properties['offset'] = (offset==undefined || offset=='') ? '0' : offset;
+ this.bounding_box = [-3,0,16,3];
+ this.update_coords();
+ }
+ Ammeter.prototype = new Component();
+ Ammeter.prototype.constructor = Ammeter;
+
+ Ammeter.prototype.toString = function() {
+ return '';
+ }
+
+ Ammeter.prototype.move_end = function() {
+ Component.prototype.move_end.call(this); // do the normal processing
+
+ // special for current probes: see if probe has been placed
+ // in the middle of wire, creating three wire segments one
+ // of which is shorting the two terminals of the probe. If
+ // so, auto remove the shorting segment.
+ var e1 = this.connections[0].location;
+ var e2 = this.connections[1].location;
+ var cplist = this.sch.find_connections(this.connections[0]);
+ for (var i = cplist.length - 1; i >= 0; --i) {
+ var c = cplist[i].parent; // a component connected to ammeter terminal
+ // look for a wire whose end points match those of the ammeter
+ if (c.type == 'w') {
+ var c_e1 = c.connections[0].location;
+ var c_e2 = c.connections[1].location;
+ if ((e1 == c_e1 && c2 == c_e2) || (e1 == c_e2 && e2 == c_e1)) {
+ c.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ Ammeter.prototype.draw = function(c) {
+ this.draw_line(c,0,0,16,0);
+
+ // draw chevron in probe color
+ c.strokeStyle = probe_colors_rgb[this.properties['color']];
+ if (c.strokeStyle != undefined) {
+ c.beginPath();
+ this.moveTo(c,6,-3);
+ this.lineTo(c,10,0);
+ this.lineTo(c,6,3);
+ c.stroke();
+ }
+ }
+
+ Ammeter.prototype.clone = function(x,y) {
+ return new Ammeter(x,y,this.rotation,this.properties['color'],this.properties['offset']);
+ }
+
+ // share code with voltage probe
+ Ammeter.prototype.edit_properties = Probe.prototype.edit_properties;
+
+ Ammeter.prototype.label = function() {
+ var name = this.properties['name'];
+ var label = 'I(' + (name ? name : '_' + this.properties['_json_']) + ')';
+ return label;
+ }
+
+ // display current for DC analysis
+ Ammeter.prototype.display_current = function(c,vmap) {
+ var label = this.label();
+ var v = vmap[label];
+ if (v != undefined) {
+ var i = engineering_notation(v,2) + 'A';
+ this.draw_text(c,i,8,-5,7,annotation_size,annotation_style);
+
+ // only display each current once
+ delete vmap[label];
+ }
+ }
+
+ // return [color, current_label, offset, type] for this probe
+ Ammeter.prototype.probe_info = function() {
+ var color = this.properties['color'];
+ var offset = this.properties['offset'];
+ if (offset==undefined || offset=="") offset = '0';
+ return [color,this.label(),offset,'current'];
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Resistor
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function Resistor(x,y,rotation,name,r) {
+ Component.call(this,'r',x,y,rotation);
+ this.properties['name'] = name;
+ this.properties['r'] = r ? r : '1';
+ this.add_connection(0,0);
+ this.add_connection(0,48);
+ this.bounding_box = [-5,0,5,48];
+ this.update_coords();
+ }
+ Resistor.prototype = new Component();
+ Resistor.prototype.constructor = Resistor;
+
+ Resistor.prototype.toString = function() {
+ return '';
+ }
+
+ Resistor.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,12);
+ this.draw_line(c,0,12,4,14);
+ this.draw_line(c,4,14,-4,18);
+ this.draw_line(c,-4,18,4,22);
+ this.draw_line(c,4,22,-4,26);
+ this.draw_line(c,-4,26,4,30);
+ this.draw_line(c,4,30,-4,34);
+ this.draw_line(c,-4,34,0,36);
+ this.draw_line(c,0,36,0,48);
+ if (this.properties['r'])
+ this.draw_text(c,this.properties['r']+'\u03A9',5,24,3,property_size);
+ if (this.properties['name'])
+ this.draw_text(c,this.properties['name'],-5,24,5,property_size);
+ }
+
+ Resistor.prototype.clone = function(x,y) {
+ return new Resistor(x,y,this.rotation,this.properties['name'],this.properties['r']);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Capacitor
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function Capacitor(x,y,rotation,name,c) {
+ Component.call(this,'c',x,y,rotation);
+ this.properties['name'] = name;
+ this.properties['c'] = c ? c : '1p';
+ this.add_connection(0,0);
+ this.add_connection(0,48);
+ this.bounding_box = [-8,0,8,48];
+ this.update_coords();
+ }
+ Capacitor.prototype = new Component();
+ Capacitor.prototype.constructor = Capacitor;
+
+ Capacitor.prototype.toString = function() {
+ return '';
+ }
+
+ Capacitor.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,22);
+ this.draw_line(c,-8,22,8,22);
+ this.draw_line(c,-8,26,8,26);
+ this.draw_line(c,0,26,0,48);
+ if (this.properties['c'])
+ this.draw_text(c,this.properties['c']+'F',9,24,3,property_size);
+ if (this.properties['name'])
+ this.draw_text(c,this.properties['name'],-9,24,5,property_size);
+ }
+
+ Capacitor.prototype.clone = function(x,y) {
+ return new Capacitor(x,y,this.rotation,this.properties['name'],this.properties['c']);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Inductor
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function Inductor(x,y,rotation,name,l) {
+ Component.call(this,'l',x,y,rotation);
+ this.properties['name'] = name;
+ this.properties['l'] = l ? l : '1n';
+ this.add_connection(0,0);
+ this.add_connection(0,48);
+ this.bounding_box = [-4,0,5,48];
+ this.update_coords();
+ }
+ Inductor.prototype = new Component();
+ Inductor.prototype.constructor = Inductor;
+
+ Inductor.prototype.toString = function() {
+ return '';
+ }
+
+ Inductor.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,14);
+ this.draw_arc(c,0,18,4,6*Math.PI/4,3*Math.PI/4);
+ this.draw_arc(c,0,24,4,5*Math.PI/4,3*Math.PI/4);
+ this.draw_arc(c,0,30,4,5*Math.PI/4,2*Math.PI/4);
+ this.draw_line(c,0,34,0,48);
+
+ if (this.properties['l'])
+ this.draw_text(c,this.properties['l']+'H',6,24,3,property_size);
+ if (this.properties['name'])
+ this.draw_text(c,this.properties['name'],-3,24,5,property_size);
+ }
+
+ Inductor.prototype.clone = function(x,y) {
+ return new Inductor(x,y,this.rotation,this.properties['name'],this.properties['l']);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Diode
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ diode_types = ['normal','ideal'];
+
+ function Diode(x,y,rotation,name,area,type) {
+ Component.call(this,'d',x,y,rotation);
+ this.properties['name'] = name;
+ this.properties['area'] = area ? area : '1';
+ this.properties['type'] = type ? type : 'normal';
+ this.add_connection(0,0); // anode
+ this.add_connection(0,48); // cathode
+ this.bounding_box = (type == 'ideal') ? [-12,0,12,48] : [-8,0,8,48];
+ this.update_coords();
+ }
+ Diode.prototype = new Component();
+ Diode.prototype.constructor = Diode;
+
+ Diode.prototype.toString = function() {
+ return '';
+ }
+
+ Diode.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,16);
+ this.draw_line(c,-8,16,8,16);
+ this.draw_line(c,-8,16,0,32);
+ this.draw_line(c,8,16,0,32);
+ this.draw_line(c,-8,32,8,32);
+ this.draw_line(c,0,32,0,48);
+
+ if (this.properties['type'] == 'ideal') {
+ // put a box around an ideal diode
+ this.draw_line(c,-10,12,10,12);
+ this.draw_line(c,-10,12,-10,36);
+ this.draw_line(c,10,12,10,36);
+ this.draw_line(c,-10,36,10,36);
+ }
+
+ if (this.properties['area'])
+ this.draw_text(c,this.properties['area'],10,24,3,property_size);
+ if (this.properties['name'])
+ this.draw_text(c,this.properties['name'],-10,24,5,property_size);
+ }
+
+ Diode.prototype.clone = function(x,y) {
+ return new Diode(x,y,this.rotation,this.properties['name'],this.properties['area'],this.properties['type']);
+ }
+
+ Diode.prototype.edit_properties = function(x,y) {
+ if (inside(this.bbox,x,y)) {
+ var fields = new Array();
+ fields['name'] = build_input('text',10,this.properties['name']);
+ fields['area'] = build_input('text',10,this.properties['area']);
+ fields['type'] = build_select(diode_types,this.properties['type']);
+
+ var content = build_table(fields);
+ content.fields = fields;
+ content.component = this;
+
+ this.sch.dialog('Edit Properties',content,function(content) {
+ content.component.properties['name'] = content.fields['name'].value;
+ content.component.properties['area'] = content.fields['area'].value;
+ content.component.properties['type'] = diode_types[content.fields['type'].selectedIndex];
+ content.component.sch.redraw_background();
+ });
+ return true;
+ } else return false;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // N-channel Mosfet
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function NFet(x,y,rotation,name,w_over_l) {
+ Component.call(this,'n',x,y,rotation);
+ this.properties['name'] = name;
+ this.properties['W/L'] = w_over_l ? w_over_l : '2';
+ this.add_connection(0,0); // drain
+ this.add_connection(-24,24); // gate
+ this.add_connection(0,48); // source
+ this.bounding_box = [-24,0,8,48];
+ this.update_coords();
+ }
+ NFet.prototype = new Component();
+ NFet.prototype.constructor = NFet;
+
+ NFet.prototype.toString = function() {
+ return '';
+ }
+
+ NFet.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,16);
+ this.draw_line(c,-8,16,0,16);
+ this.draw_line(c,-8,16,-8,32);
+ this.draw_line(c,-8,32,0,32);
+ this.draw_line(c,0,32,0,48);
+
+ this.draw_line(c,-24,24,-12,24);
+ this.draw_line(c,-12,16,-12,32);
+
+ var dim = this.properties['W/L'];
+ if (this.properties['name']) {
+ this.draw_text(c,this.properties['name'],2,22,6,property_size);
+ this.draw_text(c,dim,2,26,0,property_size);
+ } else
+ this.draw_text(c,dim,2,24,3,property_size);
+ }
+
+ NFet.prototype.clone = function(x,y) {
+ return new NFet(x,y,this.rotation,this.properties['name'],this.properties['W/L']);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // P-channel Mosfet
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function PFet(x,y,rotation,name,w_over_l) {
+ Component.call(this,'p',x,y,rotation);
+ this.properties['name'] = name;
+ this.properties['W/L'] = w_over_l ? w_over_l : '2';
+ this.add_connection(0,0); // drain
+ this.add_connection(-24,24); // gate
+ this.add_connection(0,48); // source
+ this.bounding_box = [-24,0,8,48];
+ this.update_coords();
+ }
+ PFet.prototype = new Component();
+ PFet.prototype.constructor = PFet;
+
+ PFet.prototype.toString = function() {
+ return '';
+ }
+
+ PFet.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,16);
+ this.draw_line(c,-8,16,0,16);
+ this.draw_line(c,-8,16,-8,32);
+ this.draw_line(c,-8,32,0,32);
+ this.draw_line(c,0,32,0,48);
+
+ this.draw_line(c,-24,24,-16,24);
+
+ this.draw_circle(c,-14,24,2,false);
+ this.draw_line(c,-12,16,-12,32);
+
+ var dim = this.properties['W/L'];
+ if (this.properties['name']) {
+ this.draw_text(c,this.properties['name'],2,22,6,property_size);
+ this.draw_text(c,dim,2,26,0,property_size);
+ } else
+ this.draw_text(c,dim,2,24,3,property_size);
+ }
+
+ PFet.prototype.clone = function(x,y) {
+ return new PFet(x,y,this.rotation,this.properties['name'],this.properties['W/L']);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Op Amp
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+ function OpAmp(x,y,rotation,name,A) {
+ Component.call(this,'o',x,y,rotation);
+ this.properties['name'] = name;
+ this.properties['A'] = A ? A : '30000';
+ this.add_connection(0,0); // +
+ this.add_connection(0,16); // -
+ this.add_connection(48,8); // output
+ this.add_connection(24,32); // ground
+ this.bounding_box = [0,-8,48,32];
+ this.update_coords();
+ }
+ OpAmp.prototype = new Component();
+ OpAmp.prototype.constructor = OpAmp;
+
+ OpAmp.prototype.toString = function() {
+ return '';
+ }
+
+ OpAmp.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ // triangle
+ this.draw_line(c,8,-8,8,24);
+ this.draw_line(c,8,-8,40,8);
+ this.draw_line(c,8,24,40,8);
+ // inputs and output
+ this.draw_line(c,0,0,8,0);
+ this.draw_line(c,0,16,8,16);
+ this.draw_text(c,'gnd',37,18,property_size);
+ this.draw_line(c,40,8,48,8);
+ this.draw_line(c,24,16,24,32);
+ // + and -
+ this.draw_line(c,10,0,16,0);
+ this.draw_line(c,13,-3,13,3);
+ this.draw_line(c,10,16,16,16);
+
+ if (this.properties['name'])
+ this.draw_text(c,this.properties['name'],32,16,0,property_size);
+ }
+
+ OpAmp.prototype.clone = function(x,y) {
+ return new OpAmp(x,y,this.rotation,this.properties['name'],this.properties['A']);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ // Source
+ //
+ ////////////////////////////////////////////////////////////////////////////////
+
+
+ function Source(x,y,rotation,name,type,value) {
+ Component.call(this,type,x,y,rotation);
+ this.properties['name'] = name;
+ if (value == undefined) value = 'dc(1)';
+ this.properties['value'] = value;
+ this.add_connection(0,0);
+ this.add_connection(0,48);
+ this.bounding_box = [-12,0,12,48];
+ this.update_coords();
+
+ this.content = document.createElement('div'); // used by edit_properties
+ }
+ Source.prototype = new Component();
+ Source.prototype.constructor = Source;
+
+ Source.prototype.toString = function() {
+ return '<'+this.type+'source '+this.properties['params']+' ('+this.x+','+this.y+')>';
+ }
+
+ Source.prototype.draw = function(c) {
+ Component.prototype.draw.call(this,c); // give superclass a shot
+ this.draw_line(c,0,0,0,12);
+ this.draw_circle(c,0,24,12,false);
+ this.draw_line(c,0,36,0,48);
+
+ if (this.type == 'v') { // voltage source
+ //this.draw_text(c,'+',0,12,1,property_size);
+ //this.draw_text(c,'\u2013',0,36,7,property_size); // minus sign
+ // draw + and -
+ this.draw_line(c,0,15,0,21);
+ this.draw_line(c,-3,18,3,18);
+ this.draw_line(c,-3,30,3,30);
+ // draw V
+ //this.draw_line(c,-3,20,0,28);
+ //this.draw_line(c,3,20,0,28);
+ } else if (this.type == 'i') { // current source
+ // draw arrow: pos to neg
+ this.draw_line(c,0,15,0,32);
+ this.draw_line(c,-3,26,0,32);
+ this.draw_line(c,3,26,0,32);
+ }
+
+ if (this.properties['name'])
+ this.draw_text(c,this.properties['name'],-13,24,5,property_size);
+ if (this.properties['value'])
+ this.draw_text(c,this.properties['value'],13,24,3,property_size);
+ }
+
+ // map source function name to labels for each source parameter
+ source_functions = {
+ 'dc': ['DC value'],
+
+ 'impulse': ['Height',
+ 'Width (secs)'],
+
+ 'step': ['Initial value',
+ 'Plateau value',
+ 'Delay until step (secs)',
+ 'Rise time (secs)'],
+
+ 'square': ['Initial value',
+ 'Plateau value',
+ 'Frequency (Hz)',
+ 'Duty cycle (%)'],
+
+ 'triangle': ['Initial value',
+ 'Plateau value',
+ 'Frequency (Hz)'],
+
+ 'pwl': ['Comma-separated list of alternating times and values'],
+
+ 'pwl_repeating': ['Comma-separated list of alternating times and values'],
+
+ 'pulse': ['Initial value',
+ 'Plateau value',
+ 'Delay until pulse (secs)',
+ 'Time for first transition (secs)',
+ 'Time for second transition (secs)',
+ 'Pulse width (secs)',
+ 'Period (secs)'],
+
+ 'sin': ['Offset value',
+ 'Amplitude',
+ 'Frequency (Hz)',
+ 'Delay until sin starts (secs)',
+ 'Phase offset (degrees)'],
+ }
+
+ // build property editor div
+ Source.prototype.build_content = function(src) {
+ // make an widget for each property
+ var fields = []
+ fields['name'] = build_input('text',10,this.properties['name']);
+
+ if (src == undefined) {
+ fields['value'] = this.properties['value'];
+ } else {
+ // fancy version: add select tag for source type
+ var src_types = [];
+ for (var t in source_functions) src_types.push(t);
+ var type_select = build_select(src_types,src.fun);
+ type_select.component = this;
+ type_select.addEventListener('change',source_type_changed,false)
+ fields['type'] = type_select;
+
+ if (src.fun == 'pwl' || src.run == 'pwl_repeating') {
+ var v = '';
+ var first = true;
+ for (var i = 0; i < src.args.length; i++) {
+ if (first) first = false;
+ else v += ',';
+ v += engineering_notation(src.args[i],3);
+ if (i % 2 == 0) v += 's';
+ }
+ fields[source_functions[src.fun][0]] = build_input('text',30,v);
+ } else {
+ // followed separate input tag for each parameter
+ var labels = source_functions[src.fun];
+ for (var i = 0; i < labels.length; i++) {
+ var v = engineering_notation(src.args[i],3);
+ fields[labels[i]] = build_input('text',10,v);
+ }
+ }
+ }
+
+ var div = this.content;
+ if (div.hasChildNodes())
+ div.removeChild(div.firstChild); // remove table of input fields
+ div.appendChild(build_table(fields));
+ div.fields = fields;
+ div.component = this;
+ return div;
+ }
+
+ function source_type_changed(event) {
+ if (!event) event = window.event;
+ var select = (window.event) ? event.srcElement : event.target;
+
+ // see where to get source parameters from
+ var type = select.options[select.selectedIndex].value;
+ var src = undefined;
+ if (this.src != undefined && type == this.src.fun)
+ src = this.src;
+ else if (typeof cktsim != 'undefined')
+ src = cktsim.parse_source(type+'()');
+
+ select.component.build_content(src);
+ }
+
+ Source.prototype.edit_properties = function(x,y) {
+ if (this.near(x,y)) {
+ this.src = undefined;
+ if (typeof cktsim != 'undefined')
+ this.src = cktsim.parse_source(this.properties['value']);
+ var content = this.build_content(this.src);
+
+ this.sch.dialog('Edit Properties',content,function(content) {
+ var c = content.component;
+ var fields = content.fields;
+
+ var first = true;
+ var value = '';
+ for (var label in fields) {
+ if (label == 'name')
+ c.properties['name'] = fields['name'].value;
+ else if (label == 'value') {
+ // if unknown source type
+ value = fields['value'].value;
+ c.sch.redraw_background();
+ return;
+ } else if (label == 'type') {
+ var select = fields['type'];
+ value = select.options[select.selectedIndex].value + '(';
+ } else {
+ if (first) first = false;
+ else value += ',';
+ value += fields[label].value;
+ }
+ }
+ c.properties['value'] = value + ')';
+ c.sch.redraw_background();
+ });
+ return true;
+ } else return false;
+ }
+
+
+ function VSource(x,y,rotation,name,value) {
+ Source.call(this,x,y,rotation,name,'v',value);
+ this.type = 'v';
+ }
+ VSource.prototype = new Component();
+ VSource.prototype.constructor = VSource;
+ VSource.prototype.toString = Source.prototype.toString;
+ VSource.prototype.draw = Source.prototype.draw;
+ VSource.prototype.clone = Source.prototype.clone;
+ VSource.prototype.build_content = Source.prototype.build_content;
+ VSource.prototype.edit_properties = Source.prototype.edit_properties;
+
+ // display current for DC analysis
+ VSource.prototype.display_current = function(c,vmap) {
+ var name = this.properties['name'];
+ var label = 'I(' + (name ? name : '_' + this.properties['_json_']) + ')';
+ var v = vmap[label];
+ if (v != undefined) {
+ // first draw some solid blocks in the background
+ c.globalAlpha = 0.5;
+ this.draw_text(c,'\u2588\u2588\u2588',-8,8,4,annotation_size,element_style);
+ c.globalAlpha = 1.0;
+
+ // display the element current
+ var i = engineering_notation(v,2) + 'A';
+ this.draw_text(c,i,-3,5,5,annotation_size,annotation_style);
+ // draw arrow for current
+ this.draw_line(c,-3,4,0,8);
+ this.draw_line(c,3,4,0,8);
+ // only display each current once
+ delete vmap[label];
+ }
+ }
+
+ VSource.prototype.clone = function(x,y) {
+ return new VSource(x,y,this.rotation,this.properties['name'],this.properties['value']);
+ }
+
+ function ISource(x,y,rotation,name,value) {
+ Source.call(this,x,y,rotation,name,'i',value);
+ this.type = 'i';
+ }
+ ISource.prototype = new Component();
+ ISource.prototype.constructor = ISource;
+ ISource.prototype.toString = Source.prototype.toString;
+ ISource.prototype.draw = Source.prototype.draw;
+ ISource.prototype.clone = Source.prototype.clone;
+ ISource.prototype.build_content = Source.prototype.build_content;
+ ISource.prototype.edit_properties = Source.prototype.edit_properties;
+
+ ISource.prototype.clone = function(x,y) {
+ return new ISource(x,y,this.rotation,this.properties['name'],this.properties['value']);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // JQuery slider support for setting a component value
+ //
+ ///////////////////////////////////////////////////////////////////////////////
+
+ function component_slider(event,ui) {
+ var sname = $(this).slider("option","schematic");
+
+ // set value of specified component
+ var cname = $(this).slider("option","component");
+ var pname = $(this).slider("option","property");
+ var suffix = $(this).slider("option","suffix");
+ if (typeof suffix != "string") suffix = "";
+
+ var v = ui.value;
+ $(this).slider("value",v); // move slider's indicator
+
+ var choices = $(this).slider("option","choices");
+ if (choices instanceof Array) v = choices[v];
+
+ // selector may match several schematics
+ $("." + sname).each(function(index,element) {
+ element.schematic.set_property(cname,pname,v.toString() + suffix);
+ })
+
+ // perform requested analysis
+ var analysis = $(this).slider("option","analysis");
+ if (analysis == "dc")
+ $("." + sname).each(function(index,element) {
+ element.schematic.dc_analysis();
+ })
+
+ return false;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////
+ //
+ // Module definition
+ //
+ ///////////////////////////////////////////////////////////////////////////////
+
+ var module = {
+ 'Schematic': Schematic,
+ 'component_slider': component_slider,
+ }
+ return module;
+ }());
diff --git a/common/test/data/full/problem/Circuit_Sandbox.xml b/common/test/data/full/problem/Circuit_Sandbox.xml
new file mode 100644
index 0000000000..89625f447b
--- /dev/null
+++ b/common/test/data/full/problem/Circuit_Sandbox.xml
@@ -0,0 +1,6 @@
+
Here's a sandbox where you can experiment with all the components
+we'll discuss in 6.002x. If you click on CHECK below, your diagram
+will be saved on the server and you can return at some later time.
+
+correct = ['correct']
+
diff --git a/common/test/data/full/problem/H1P1_Energy.xml b/common/test/data/full/problem/H1P1_Energy.xml
new file mode 100644
index 0000000000..18e71622d3
--- /dev/null
+++ b/common/test/data/full/problem/H1P1_Energy.xml
@@ -0,0 +1,32 @@
+
+According to McDonald's
+nutrition
+facts a \($M\) provides \($C\) kiloCalories of energy. (1 dietitian's
+calorie is 1000 physicist's calories.) A typical automobile lead-acid
+battery can supply 50 Ampere-hours at 12 Volts. We can compare these by
+converting the energy specifications to common units. The SI unit of
+energy is the Joule=1 Watt-second. One physicist's calorie is 4.2 Joules.
+
+
+
+How many Joules are there in a \($M\)?
+
+
+
+
+How many Joules are there in a car battery?
+
+
+
+
+Which tastes better? :-)
+
+
+
diff --git a/common/test/data/full/problem/H1P2_Duality.xml b/common/test/data/full/problem/H1P2_Duality.xml
new file mode 100644
index 0000000000..4b1812c0b7
--- /dev/null
+++ b/common/test/data/full/problem/H1P2_Duality.xml
@@ -0,0 +1,127 @@
+
+In this problem we will investigate a fun idea called "duality."
+
+Consider the series circuit in the diagram shown.
+
+We are given device parameters \(V=$V\)V, \(R_1=$R1\Omega\), and \(R_2=$R2\Omega\).
+All of the unknown voltages and currents are labeled in associated reference
+directions. Solve this circuit for the unknowns and enter them into
+the boxes given.
+
+The value (in Volts) of \(v_1\) is:
+
+
+
+
+The value (in Amperes) of \(i_1\) is:
+
+
+
+
+The value (in Volts) of \(v_2\) is:
+
+
+
+
+The value (in Amperes) of \(i_2\) is:
+
+
+
+
+The value (in Volts) of \(v_V\) is:
+
+
+
+
+The value (in Amperes) of \(i_V\) is:
+
+
+
+
+The sum of the powers (in Watts) entering all of the elements is:
+
+
+
+
+ Now, let's turn our attentions to the parallel circuit.
+The numerical value of the strength of the current source is set to be
+the same as the numerical value of the strength of the voltage source:
+\(I=$I\)A. The resistances are set to be reciprocals of the
+resistances in the series circuit: \(R_3=$R3\Omega\) and
+\(R_4=$R4\Omega\).
+
+Solve this circuit for the unknowns and enter them into
+the boxes given.
+
+The value (in Volts) of \(v_3\) is:
+
+
+
+
+The value (in Amperes) of \(i_3\) is:
+
+
+
+
+The value (in Volts) of \(v_4\) is:
+
+
+
+
+The value (in Amperes) of \(i_4\) is:
+
+
+
+
+The value (in Volts) of \(v_I\) is:
+
+
+
+
+The value of (in Amperes) \(i_I\) is:
+
+
+
+
+The sum of the powers (in Watts) entering all of the elements is:
+
+
+
+
+ Look carefully at the numbers you have derived.
+Compare the series and parallel circuit. Do you see the pattern?
+
+
+
+
diff --git a/common/test/data/full/problem/H1P3_Poor_Workmanship.xml b/common/test/data/full/problem/H1P3_Poor_Workmanship.xml
new file mode 100644
index 0000000000..cf9db4053a
--- /dev/null
+++ b/common/test/data/full/problem/H1P3_Poor_Workmanship.xml
@@ -0,0 +1,84 @@
+
+Joe wants to heat his 12'X20' workshop with electric heat. He has
+hired the HACME electrician company to build the system. They propose
+to use three \($P\)W 240V baseboard heaters to provide a total heating
+capacity of \($Pmax\)W. (A heater is basically a
+resistor. This is not quite true, because there is a thermostatic
+switch incorporated into the heater and because the resistance of a
+heater varies a bit with its temperature. But we will use a linear
+resistor as a model of a heater.)
+
+In the proposed system the heaters are connected in parallel with the
+240V 60Hz AC power line (modeled by a voltage source) as shown in the
+diagram:
+
+Remember (from exercise S1E3: AC POWER) that AC power-line voltages
+and currents are specified as RMS values. So 120V AC heats a given
+resistance exactly as much as 120V DC would heat that same
+resistance.
+
+
+
+
+How much current is expected to be drawn from the power line by this
+heating system when all three heaters are on?
+
+
+
+
+If instead, HACME chose to implement the system with 120V heaters, how
+much current would have been needed?
+
+
+
+
+Notice that this would require much heavier and more expensive wire to
+distribute the power.
+
+Back to the original plan with 240V power.
+
+Unfortunately, Sparky, who works for HACME, was a little sleepy that
+day. He accidentally connected the heaters as shown below:
+
+
+As a consequence, Joe found his workshop too cold. H1 was weak; H2
+and H3 barely warmed up.
+
+What power was being dissipated in H1?
+
+
+
+
+What power was being dissipated in H2 (or in H3)?
+
+
+
+
+So the total heating power in Joe's shop was:
+
+
+
+
+No wonder Joe was cold.
+
+
+
diff --git a/common/test/data/full/problem/Lab_0_Using_the_Tools.xml b/common/test/data/full/problem/Lab_0_Using_the_Tools.xml
new file mode 100644
index 0000000000..b5f593c294
--- /dev/null
+++ b/common/test/data/full/problem/Lab_0_Using_the_Tools.xml
@@ -0,0 +1,115 @@
+ In this first lab, the goal is to familiarize yourself with the
+interactive tool that will be used to enter and analyze circuits. Here's
+the example schematic used below:
+
+
+Figure 1. Test Circuit for Lab 0
+ And here's the interactive tool to be used in the tasks that follow:
+
+
+correct = ['correct']
+ Please do the following tasks. When you're done, remember to click CHECK to preserve
+your work and to verify your answers.
+
+Enter schematic. Add the voltage source, resistors,
+wires and ground reference to your diagram, connected as shown
+in Figure 1 .Measure voltages. Use the tool to run a DC analysis on
+the circuit, i.e.., click on the DC button in the toolbar. This will
+annotate each node/voltage source in the circuit with a
+voltage/current. If the components are wired correctly, the
+voltage for node D should be reported as 0V since it's
+connected to the ground reference and the voltage for node A
+should be reported as 3V since it is connected directly to the
+positive terminal of a 3V voltage source whose negative terminal
+is connected to ground. Please enter the voltages reported for nodes
+B and C.
+
+
+Voltage for node B (volts):
+
+
+
+
+Voltage for node C (volts):
+
+
+
+Measure current.
+
+The DC analysis annotates each voltage source with the current it is
+providing to the rest of the circuit. The simulator denotes the sign
+of the source current as positive if the current is flowing out of the
+positive terminal of the source, through the rest of the circuit, and
+then back in to the negative terminal. And the source current sign is
+negative if the current is flowing out of the negative terminal of the
+source, through the circuit, and into the positive terminal of the
+source. Please report the total resistance of the path connecting the
+positive and negative terminals of the source, the signed current
+flowing from the source into the circuit, and (using the voltage
+value for the source) verify that Ohm's law is obeyed.
+
+
+Total resistance between pos and neg terminals (ohms):
+
+
+
+
+Current flowing from source into the circuit (amps, with correct sign):
+
+
+
+Verify KVL. The four components in the schematic form a
+large loop -- let's verify that Kirchoff's Voltage Law holds by
+summing the voltage changes across the devices. Starting with node A,
+travel clockwise around the loop of components, entering the voltage
+change across each component. Choose the sign of the change for a
+component using the first terminal you come to in the clockwise
+traversal as the reference node. Verify that these four values sum
+to 0.
+
+
+
+Voltage change across R1 (volts, with correct sign):
+
+
+
+
+Voltage change across R2 (volts, with correct sign):
+
+
+
+
+Voltage change across R3 (volts, with correct sign):
+
+
+
+
+Voltage change across Vsupply (volts, with correct sign):
+
+
+Transient analysis. Change the waveform produced by
+the voltage source from a DC value of 3V to a sinusoid with a 1V
+amplitude, an offset voltage of 1V, and a frequency of 1kHz. Add
+scope probes to nodes A, B and C and edit their properties so that the
+plots will be different colors. Now run a transient analysis for 5ms.
+Move the mouse over the plot until the marker (a vertical dashed line
+that follows the mouse when it's over the plot) is at approximately
+1.25ms. Please report the measured voltages for nodes A, B and C.
+
+
+
+Voltage for node A (volts):
+
+
+
+
+Voltage for node B (volts):
+
+
+
+
+Voltage for node C (volts):
+
+
+
+
diff --git a/common/test/data/full/problem/Resistor_Divider.xml b/common/test/data/full/problem/Resistor_Divider.xml
new file mode 100644
index 0000000000..a5d6630c79
--- /dev/null
+++ b/common/test/data/full/problem/Resistor_Divider.xml
@@ -0,0 +1,59 @@
+ You have a 6-volt battery (assumed ideal) and a 1.5-volt flashlight
+bulb, which is known to draw \(0.5 A\) when the bulb voltage is \(1.5 V\) (see
+figure below). Design a network of resistors to go between the battery
+and the bulb to give \(v_s = 1.5 V\) when the bulb is connected, yet
+ensures that \(v_s\) does not rise above \(2 V\) when the bulb is disconnected.
+
+Hint : use a two-resistor voltage divider to create the voltage for node A. You'll
+have two unknowns (R1 and R2) which can be determined by solving the two equations for
+\(v_s\) derived from the constraints above: one involving R1, R2 and Rbulb where \(v_s = 1.5\), and
+one involving R1 and R2 where \(v_s = 2\).
+
+ There are two schematic diagrams below. Please enter the network
+of resistors you've designed into both diagrams. The top diagram is
+the model when the bulb is connected; the bottom diagram is the model
+when the bulb is disconnected.
+
+ Run a DC analysis on both diagrams to
+show that the node labeled "A" has a voltage of approximately
+\(1.5 V\) in the top diagram and less than \(2 V\) in the bottom
+adiagram. Submit your results after the DC analyses have
+been run (so the results of the analyses will be submitted too).
+
+
+
+Schematic model when bulb is connected:
+
+
+
+Schematic model when bulb is disconnected:
+
+
+
+
+
+# for a schematic response, submission[i] is the json representation
+# of the diagram and analysis results for the i-th schematic tag
+
+correct = ['incorrect', 'incorrect'] # optimistic default :)
+
+def get_dc(json):
+ for element in json:
+ if element[0] == 'dc': return element[1]
+ return None
+
+dc_with_bulb = get_dc(submission[0])
+if dc_with_bulb:
+ v = dc_with_bulb['A']
+ if v >= 1.4 and v <= 1.6: # want 1.5
+ correct[0] = 'correct'
+
+dc_without_bulb = get_dc(submission[1])
+if dc_without_bulb:
+ v = dc_without_bulb['A']
+ if v <= 2.1: # want 2
+ correct[1] = 'correct'
+
+
+
+
diff --git a/common/test/data/full/problem/S1E3_AC_power.xml b/common/test/data/full/problem/S1E3_AC_power.xml
new file mode 100644
index 0000000000..b818d936f5
--- /dev/null
+++ b/common/test/data/full/problem/S1E3_AC_power.xml
@@ -0,0 +1,40 @@
+
+The plot shows 1/10 second of the voltage waveform of a 120V 60Hz AC
+(Alternating Current)
+power circuit, like that delivered to residences in the United States.
+
+The actual voltage is \(120*sqrt(2)*cos(2*\pi*60*t)\)Volts. If we apply
+this voltage across a resistor of resistance \($R\Omega\) the resistor
+will dissipate a time-varying power.
+What is the peak power (in Watts) dissipated by the resistor?
+
+
+
+
+ What is the average power (in Watts) dissipated by the resistor? (Hint: you
+ compute the average power by integrating the power over one cycle of
+ the waveform.)
+
+
+
+
+ What would be the power (in Watts) dissipated by the resistor if the voltage
+ was a constant value of 120V?
+
+
+
+
+ If a time-varying voltage dissipates the same power in a resistor
+as a constant voltage would dissipate, we say that the time-varying voltage has
+the RMS value of the constant. RMS is an abbreviation for Root-Mean-Square.
+
+
+
+
diff --git a/common/test/data/full/problem/Sample_Algebraic_Problem.xml b/common/test/data/full/problem/Sample_Algebraic_Problem.xml
new file mode 100644
index 0000000000..7bea1cc92e
--- /dev/null
+++ b/common/test/data/full/problem/Sample_Algebraic_Problem.xml
@@ -0,0 +1,13 @@
+
+Enter the algebraic expression \(A x^2 + \sqrt{y}\) in the box below. The
+entry is case sensitive. The product must be indicated with an
+asterisk, and the exponentation with a caret, so you must write
+"A*x^2 + sqrt(y)".
+
+
+
+
diff --git a/common/test/data/full/problem/Sample_Numeric_Problem.xml b/common/test/data/full/problem/Sample_Numeric_Problem.xml
new file mode 100644
index 0000000000..f41881a028
--- /dev/null
+++ b/common/test/data/full/problem/Sample_Numeric_Problem.xml
@@ -0,0 +1,8 @@
+
+Enter the numerical value of the expression \(x + y\) where
+\(x = 3\) and \(y = 5\).
+
+
+
+
+
diff --git a/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml b/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml
new file mode 100644
index 0000000000..28b56fb9b0
--- /dev/null
+++ b/common/test/data/full/sequential/Administrivia_and_Circuit_Elements.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ discuss
+
+ S1E4 has been removed.
+
+
+
+
+ Minor correction: Six elements (five resistors)
+
+ discuss
+
+
+
diff --git a/common/test/data/full/sequential/System_Usage_Sequence.xml b/common/test/data/full/sequential/System_Usage_Sequence.xml
new file mode 100644
index 0000000000..baf73f1e9e
--- /dev/null
+++ b/common/test/data/full/sequential/System_Usage_Sequence.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/common/test/data/full/static/js/sound_labs/circuit.js b/common/test/data/full/static/js/sound_labs/circuit.js
new file mode 100644
index 0000000000..dfd756a4a2
--- /dev/null
+++ b/common/test/data/full/static/js/sound_labs/circuit.js
@@ -0,0 +1,1247 @@
+var Circuit = (function() {
+
+ var Color =
+ {
+ background : "rgb(0, 51, 102)", //0.0, 0.2, 0.4
+ black : "rgb(0, 0, 0)", //0.0
+ lodarkgray : "rgb(26, 26, 26)", //0.1 = 25.5
+ darkgray : "rgb(51, 51, 51)", //0.2
+ lomidgray : "rgb(102, 102, 102)", //0.4
+ midgray : "rgb(128, 128, 128)", //0.5 = 127.5
+ himidgray : "rgb(153, 153, 153)", //0.6
+ litegray : "rgb(204, 204, 204)", //0.8
+ white : "rgb(255, 255, 255)", //1.0
+
+ red : "rgb(255, 0, 0)",
+ green : "rgb(0, 255, 0)",
+ blue : "rgb(0, 0, 255)",
+ yellow : "rgb(255, 255, 0)",
+ cyan : "rgb(0, 255, 255)",
+ magenta : "rgb(255, 0, 255)"
+ };
+
+ var Utils =
+ {
+ TWO_PI: 2.0*Math.PI,
+ PI_DIV_2: Math.PI/2.0
+ };
+
+ function distance(x1, y1, x2, y2)
+ {
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+
+ function transform(x, y, xt, yt, rot)
+ {
+ //First translate
+ x -= xt;
+ y -= yt;
+ //Then rotate
+ return {x: x * Math.cos(rot) - y * Math.sin(rot), y: x * Math.sin(rot) + y * Math.cos(rot)};
+ }
+
+ function closestGridPoint(gridStep, x)
+ {
+ return gridStep * Math.round(x / gridStep);
+ }
+
+ function getMousePosition(diagram, event)
+ {
+ var mouseX = event.pageX - (parseInt(diagram.element.offset().left) + parseInt(diagram.element.css('paddingLeft')) + parseInt(diagram.element.css('borderLeftWidth')));
+ var mouseY = event.pageY - (parseInt(diagram.element.offset().top) + parseInt(diagram.element.css('paddingTop')) + parseInt(diagram.element.css('borderTopWidth')));
+ return {x : mouseX, y : mouseY};
+ }
+
+ function diagramMouseDown(event)
+ {
+ if (!event) event = window.event;
+ else event.preventDefault();
+ var canvas = (window.event) ? event.srcElement : event.target;
+ var diagram = canvas.diagram;
+ var mpos = getMousePosition(diagram, event);
+
+ for(var i = 0, len = diagram.components.length; i < len; i++)
+ {
+ if(diagram.components[i].isInside(mpos.x, mpos.y))
+ {
+ diagram.components[i].selected = true;
+ diagram.startx = closestGridPoint(diagram.gridStep, mpos.x);
+ diagram.starty = closestGridPoint(diagram.gridStep, mpos.y);
+ }
+ }
+
+ return false;
+ }
+
+ function diagramMouseMove(event)
+ {
+ if (!event) event = window.event;
+ else event.preventDefault();
+ var canvas = (window.event) ? event.srcElement : event.target;
+ var diagram = canvas.diagram;
+ var mpos = getMousePosition(diagram, event);
+ var componentSelected = false;
+
+ //First check if any component if selected
+ for(var i = 0, len = diagram.components.length; i < len; i++)
+ {
+ if(diagram.components[i].selected)
+ {
+ diagram.endx = closestGridPoint(diagram.gridStep, mpos.x);
+ diagram.components[i].x += (diagram.endx - diagram.startx);
+ diagram.startx = diagram.endx;
+ diagram.endy = closestGridPoint(diagram.gridStep, mpos.y);
+ diagram.components[i].y += (diagram.endy - diagram.starty);
+ diagram.starty = diagram.endy;
+ diagram.paint();
+ componentSelected = true;
+ }
+ }
+
+ if(!componentSelected)
+ {
+ for(var i = 0, len = diagram.components.length; i < len; i++)
+ {
+ if(diagram.components[i].isInside(mpos.x, mpos.y))
+ diagram.components[i].selectable = true;
+ else
+ diagram.components[i].selectable = false;
+ //Repaint only once, on a mouse enter or mouse leave
+ if(diagram.components[i].previousSelectable != diagram.components[i].selectable)
+ {
+ diagram.components[i].previousSelectable = diagram.components[i].selectable;
+ diagram.paint();
+ }
+ }
+ }
+
+ return false;
+ }
+
+ function diagramMouseUp(event)
+ {
+ if (!event) event = window.event;
+ else event.preventDefault();
+ var canvas = (window.event) ? event.srcElement : event.target;
+ var diagram = canvas.diagram;
+ var mpos = getMousePosition(diagram, event);
+
+ for(var i = 0, len = diagram.components.length; i < len; i++)
+ {
+ //Unselect all
+ diagram.components[i].selected = false;
+ }
+ diagram.startx = 0;
+ diagram.endx = diagram.startx;
+ diagram.starty = 0;
+ diagram.endx = diagram.starty;
+
+ return false;
+ }
+
+ function diagramDoubleClick(event)
+ {
+ if (!event) event = window.event;
+ else event.preventDefault();
+ var canvas = (window.event) ? event.srcElement : event.target;
+ var diagram = canvas.diagram;
+
+ alert(diagram.toString());
+
+ return false;
+ }
+
+ function copyPrototype(descendant, parent)
+ {
+ var sConstructor = parent.toString();
+ var aMatch = sConstructor.match(/\s*function (.*)\(/);
+ if(aMatch != null)
+ {
+ descendant.prototype[aMatch[1]] = parent;
+ }
+ for(var m in parent.prototype)
+ {
+ descendant.prototype[m] = parent.prototype[m];
+ }
+ }
+
+ function Diagram(element, frozen)
+ {
+ this.element = element;
+ this.frozen = frozen;
+ this.canvas = element[0];
+ this.canvas.diagram = this;
+ this.width = this.canvas.width;
+ this.height = this.canvas.height;
+ this.ctx = this.canvas.getContext("2d");
+ this.background = Color.black;
+ if (!this.frozen)
+ {
+ this.canvas.addEventListener('mousedown', diagramMouseDown, false);
+ this.canvas.addEventListener('mousemove', diagramMouseMove, false);
+ this.canvas.addEventListener('mouseup', diagramMouseUp, false);
+ this.canvas.addEventListener('dblclick', diagramDoubleClick, false);
+ }
+ //To disable text selection outside the canvas
+ this.canvas.onselectstart = function(){return false;};
+ this.components = [];
+ this.gridStep = 5;
+ this.startx = 0;
+ this.endx = 0;
+ this.starty = 0;
+ this.endy = 0;
+ this.showGrid = false;
+ this.xGridMin = 10;
+ this.xGridMax = 500;
+ this.yGridMin = 10;
+ this.yGridMax = 500;
+ this.xOrigin = 0;
+ this.yOrigin = 0;
+ this.scale = 2; //Scaling is the same in x and y directions
+ this.fontSize = 6;
+ this.fontType = 'sans-serif';
+ }
+
+ Diagram.prototype.toString = function()
+ {
+ var result = "";
+ for(var i = 0, len = this.components.length; i < len; i++)
+ {
+ result += this.components[i].toString();
+ }
+
+ return result;
+ }
+
+ Diagram.prototype.addNode = function(x, y)
+ {
+ var n = new Node(x, y);
+ n.ctx = this.ctx;
+ n.diagram = this;
+ n.updateBoundingBox();
+ this.components.push(n);
+ return n;
+ }
+
+ Diagram.prototype.addWire = function(x1, y1, x2, y2)
+ {
+ var w = new Wire(x1, y1, x2, y2)
+ w.ctx = this.ctx;
+ w.diagram = this;
+ w.updateBoundingBox();
+ this.components.push(w);
+ return w;
+ }
+
+ Diagram.prototype.addLabel = function(x, y, value, textAlign)
+ {
+ var l = new Label(x, y, value, textAlign)
+ l.ctx = this.ctx;
+ l.diagram = this;
+ l.updateBoundingBox();
+ this.components.push(l);
+ return l;
+ }
+
+ Diagram.prototype.addResistor = function(x, y, value)
+ {
+ var r = new Resistor(x, y, value)
+ r.ctx = this.ctx;
+ r.diagram = this;
+ r.updateBoundingBox();
+ this.components.push(r);
+ return r;
+ }
+
+ Diagram.prototype.addInductor = function(x, y, value)
+ {
+ var l = new Inductor(x, y, value)
+ l.ctx = this.ctx;
+ l.diagram = this;
+ l.updateBoundingBox();
+ this.components.push(l);
+ return l;
+ }
+
+ Diagram.prototype.addCapacitor = function(x, y, value)
+ {
+ var c = new Capacitor(x, y, value)
+ c.ctx = this.ctx;
+ c.diagram = this;
+ c.updateBoundingBox();
+ this.components.push(c);
+ return c;
+ }
+
+ Diagram.prototype.addMosfet = function(x, y, value, type)
+ {
+ var m = new Mosfet(x, y, value, type)
+ m.ctx = this.ctx;
+ m.diagram = this;
+ m.updateBoundingBox();
+ this.components.push(m);
+ return m;
+ }
+
+ Diagram.prototype.addGround = function(x, y)
+ {
+ var g = new Ground(x, y)
+ g.ctx = this.ctx;
+ g.diagram = this;
+ g.updateBoundingBox();
+ this.components.push(g);
+ return g;
+ }
+
+ Diagram.prototype.addDiode = function(x, y, value)
+ {
+ var d = new Diode(x, y, value)
+ d.ctx = this.ctx;
+ d.diagram = this;
+ d.updateBoundingBox();
+ this.components.push(d);
+ return d;
+ }
+
+ Diagram.prototype.addSource = function(x, y, value, type)
+ {
+ var v = new Source(x, y, value, type)
+ v.ctx = this.ctx;
+ v.diagram = this;
+ v.updateBoundingBox();
+ this.components.push(v);
+ return v;
+ }
+
+ Diagram.prototype.paint = function()
+ {
+ this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
+ if (this.showGrid)
+ this.drawGrid();
+
+ for(var i = 0, len = this.components.length; i < len; i++)
+ {
+ this.components[i].paint();
+ }
+ }
+
+ Diagram.prototype.drawGrid = function()
+ {
+ this.ctx.fillStyle = Color.black;
+ for(x = this.xGridMin; x <= this.xGridMax; x += this.gridStep)
+ {
+ for( y = this.yGridMin; y <= this.yGridMax; y += this.gridStep)
+ {
+ this.drawPixel(this.ctx, x, y);
+ }
+ }
+ }
+ //Drawing routines from schematic
+ Diagram.prototype.drawLine = function(c, x1, y1, x2, y2)
+ {
+ c.beginPath();
+ c.moveTo((x1 - this.xOrigin) * this.scale, (y1 - this.yOrigin) * this.scale);
+ c.lineTo((x2 - this.xOrigin) * this.scale, (y2 - this.yOrigin) * this.scale);
+ c.stroke();
+ }
+
+ Diagram.prototype.drawArc = function(c, x, y, radius,startRadians, endRadians, anticlockwise, width, filled)
+ {
+ c.lineWidth = width;
+ c.beginPath();
+ c.arc((x - this.xOrigin)*this.scale, (y - this.yOrigin)*this.scale, radius*this.scale, startRadians, endRadians, anticlockwise);
+ if (filled) c.fill();
+ else c.stroke();
+ }
+
+ Diagram.prototype.drawCircle = function(c, x, y, radius, filled)
+ {
+ this.drawArc(c, x, y, radius, 0, 2*Math.PI, false, 1, filled);
+ }
+
+ Diagram.prototype.drawText = function(c, str, x, y)
+ {
+ c.font = this.scale*this.fontSize + "pt " + this.fontType;
+ c.fillText(str, (x - this.xOrigin) * this.scale, (y - this.yOrigin) * this.scale);
+ }
+ //End drawing routines
+
+ Diagram.prototype.parseSubSuperScriptText = function(str)
+ {
+ /*var regExpSub = /_\{(.*?)\}/g;
+ var regExpSup = /\^\{(.*?)\}/g;
+ var subs = [];
+ var sups = [];
+ var text = [];
+ var finalText = [];
+ var isSub = false;
+ var isSup = false;
+
+ subs = str.match(regExpSub);
+ for (var i = 0; i < subs.length; i++)
+ {
+ subs[i] = subs[i].substring(2, subs[i].length - 1); //Discard _{ and }
+ }
+
+ sups = str.match(regExpSup);
+ for (var i = 0; i < sups.length; i++)
+ {
+ sups[i] = sups[i].substring(2, sups[i].length - 1); //Discard ^{ and }
+ }*/
+
+ var len = str.length;
+ var i = 0;
+ var start;
+ var end;
+ found = false;
+ var text = [];
+ var type;
+ var ntext = "";
+
+ while (i < len)
+ {
+ if (str[i] == "_") //Encountered a potential subscript _
+ type = "sub";
+ else if (str[i] == "^") //Encountered a potential superscript ^
+ type = "sup";
+
+ if (type == "sub" || type == "sup")
+ {
+ if (str[i+1] == "{")
+ {
+ i += 2; //Discard _{ or ^{
+ start = i;
+ found = false;
+ while (i < len) //Look for }
+ {
+ if (str[i] == "}")
+ {
+ found = true;
+ end = i;
+ break;
+ }
+ i++;
+ }
+ if (found && end > start) //Discard empty subscript ie _{}
+ {
+ //Store previous normal text if not empty and tag it as so
+ if (ntext.length != 0)
+ {
+ text.push({s: ntext, type: "normal"});
+ ntext = "";
+ }
+ //Store subscript or superscript and tag it as so
+ if (type == "sub")
+ text.push({s: str.substring(start, end), type: "sub"});
+ else if (type == "sup")
+ text.push({s: str.substring(start, end), type: "sup"});
+ i = end + 1;
+ }
+ else
+ i = start - 2; //Nothing was found, backtrack to _ or ^
+ }
+ }
+ ntext += str[i];
+ if (i == len - 1 && ntext.length != 0) //We've reached the end, store normal text if not empty and tag it as so
+ text.push({s: ntext, type: "normal"});
+ i++;
+ }
+
+ return text;
+ }
+
+ Diagram.prototype.subSuperScriptLength = function(c, text)
+ {
+ var fontNormal = this.scale*this.fontSize + "pt " + this.fontType;
+ var fontSubSup = this.scale*(this.fontSize-2) + "pt " + this.fontType;
+
+ var xpos = 0;
+
+ for (var i = 0; i < text.length; i++)
+ {
+ if (text[i].type == "normal")
+ c.font = fontNormal;
+ else if (text[i].type == "sub")
+ c.font = fontSubSup;
+ else
+ c.font = fontSubSup;
+ xpos += c.measureText(text[i].s).width;
+ }
+
+ return xpos;
+ }
+
+ Diagram.prototype.drawSubSuperScript = function(c, str, x, y, way)
+ {
+ var fontNormal = this.scale*this.fontSize + "pt " + this.fontType;
+ var fontSubSup = this.scale*(this.fontSize-2) + "pt " + this.fontType;
+
+ var text = this.parseSubSuperScriptText(str);
+ var len = this.subSuperScriptLength(c, text);
+ var xposIni = (x - this.xOrigin) * this.scale;
+ var yposIni = (y - this.yOrigin) * this.scale;
+ var xpos, ypos;
+
+ if (way == "left")
+ xpos = xposIni;
+ else if (way == "right")
+ xpos = xposIni - len;
+ else if (way == "center")
+ xpos = xposIni - len/2;
+
+ //Draw the text
+ for (var i = 0; i < text.length; i++)
+ {
+ if (text[i].type == "normal")
+ {
+ c.font = fontNormal;
+ ypos = yposIni;
+ }
+ else if (text[i].type == "sub")
+ {
+ c.font = fontSubSup;
+ ypos = yposIni + 3;
+ }
+ else
+ {
+ c.font = fontSubSup;
+ ypos = yposIni - 5;
+ }
+ c.fillText(text[i].s, xpos, ypos);
+ //Advance x position
+ xpos += c.measureText(text[i].s).width;
+ }
+ }
+
+ //Draws a rectangle, top left corner x1, y1 and bottom right corner x2, y2
+ Diagram.prototype.drawCrispLine = function(c, x1, y1, x2, y2)
+ {
+ c.beginPath();
+ c.moveTo(x1 + 0.5, y1 + 0.5);
+ c.lineTo(x2 + 0.5, y2 + 0.5);
+ c.stroke();
+ }
+
+ Diagram.prototype.drawRect = function(c, x1, y1, x2, y2)
+ {
+ c.strokeRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0);
+ }
+
+ Diagram.prototype.fillRect = function(c, x1, y1, x2, y2)
+ {
+ c.fillRect(x1, y1, x2 - x1 + 1.0, y2 - y1 + 1.0);
+ }
+
+ Diagram.prototype.clearRect = function(c, x1, y1, x2, y2)
+ {
+ c.clearRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0);
+ }
+
+ Diagram.prototype.drawPixel = function(c, x, y)
+ {
+ c.fillRect(x, y, 1.0, 1.0);
+ }
+
+ Diagram.prototype.drawPoint = function(c, x, y, radius)
+ {
+ c.beginPath();
+ c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise
+ c.fill();
+ }
+
+ Diagram.prototype.drawHollowPoint = function(c, x, y, radius)
+ {
+ c.beginPath();
+ c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise
+ c.stroke();
+ }
+
+ Diagram.prototype.drawTriangle = function(c, x1, y1, x2, y2, x3, y3)
+ {
+ c.beginPath();
+ c.moveTo(x1 + 0.5, y1 + 0.5);
+ c.lineTo(x2 + 0.5, y2 + 0.5);
+ c.lineTo(x3 + 0.5, y3 + 0.5);
+ c.closePath();
+ c.stroke();
+ }
+
+ Diagram.prototype.fillTriangle = function(c, x1, y1, x2, y2, x3, y3)
+ {
+ c.beginPath();
+ c.moveTo(x1 + 0.5, y1 + 0.5);
+ c.lineTo(x2 + 0.5, y2 + 0.5);
+ c.lineTo(x3 + 0.5, y3 + 0.5);
+ c.closePath();
+ c.fill();
+ }
+
+ Diagram.prototype.drawHalfCircle = function(c, x, y, radius, concaveDown) //For inductance only
+ {
+ c.beginPath();
+ if (concaveDown)
+ c.arc(x + 0.5, y + 0.5, radius, 0, Math.PI, true); //Last param is anticlockwise
+ else
+ c.arc(x + 0.5, y + 0.5, radius, Math.PI, 0, true); //Last param is anticlockwise
+ c.stroke();
+ }
+
+ Diagram.prototype.drawDiamond = function(c, x, y, h)
+ {
+ var xc = x + 0.5;
+ var yc = y + 0.5;
+
+ c.beginPath();
+ c.moveTo(xc-h, yc);
+ c.lineTo(xc, yc-h);
+ c.lineTo(xc+h, yc);
+ c.lineTo(xc, yc+h);
+ c.closePath();
+
+ c.fill();
+ }
+
+ Diagram.prototype.drawX = function(c, x, y, h)
+ {
+ var xc = x + 0.5;
+ var yc = y + 0.5;
+
+ c.beginPath();
+ c.moveTo(xc+h, yc-h);
+ c.lineTo(xc-h, yc+h);
+ c.moveTo(xc-h, yc-h);
+ c.lineTo(xc+h, yc+h);
+ c.stroke();
+ }
+
+ Diagram.prototype.drawArrow = function(c, x1, y1, x2, y2, base, height)
+ {
+ var xs1 = x1 + 0.5;
+ var ys1 = y1 + 0.5;
+ var xs2 = x2 + 0.5;
+ var ys2 = y2 + 0.5;
+ var xv = x2 - x1;
+ var yv = y2 - y1;
+ var ang = Math.atan2(-yv, xv);
+
+ c.beginPath();
+ //Arrow line
+ c.moveTo(xs1, ys1);
+ c.lineTo(xs2, ys2);
+ c.stroke();
+ //Arrow head, first draw a triangle with top on origin then translate/rotate to orient and fit on line
+ c.save();
+ c.beginPath();
+ c.translate(xs2, ys2);
+ c.rotate(Utils.PI_DIV_2-ang);
+
+ c.moveTo(0, 0);
+ c.lineTo(-base, height);
+ c.lineTo(base, height);
+ c.closePath();
+ c.fill();
+ //c.stroke();
+ c.restore();
+ }
+
+ //***** COMPONENT *****//
+ function Component(x, y, width, height)
+ {
+ this.x = x;
+ this.y = y;
+
+ this.boundingBox = [0, 0, 0, 0];
+ this.transBoundingBox = [0, 0, 0, 0];
+ this.xMiddle = 0;
+ this.yMiddle = 0;
+
+ this.previousSelectable = false;
+ this.selectable = false;
+ this.selected = false;
+ this.ctx;
+ this.diagram;
+ this.color = Color.white;
+ this.selectedColor = Color.red;
+ this.eventListeners = {};
+ //Label to the left
+ this.label = {str: "", x: 0, y: 0, position: "left", show: true, color: Color.white}; //color: Color.lodarkgray
+ //String representing value to the right
+ this.valueString = {x: 0, y: 0, position: "right", show: true, suffix: "", decimal: -1, color: Color.white}; //color: Color.lodarkgray
+
+ this.lineWidth = 1;
+ this.rotation = 0;
+ this.value = 0;
+ }
+
+ Component.prototype.addEventListener = function(type, eventListener)
+ {
+ if(!(type in this.eventListeners))
+ this.eventListeners[type] = eventListener;
+ }
+
+ Component.prototype.removeEventListener = function(type, eventListener)
+ {
+ for(var i in this.eventListeners)
+ {
+ if(this.eventListeners[i] === eventListener)
+ delete this.eventListeners[i].eventListener;
+ }
+ }
+
+ Component.prototype.fireEvent = function(event)
+ {
+ if( typeof event == "string")
+ (this.eventListeners[event])();
+ else
+ throw new Error("Event object missing 'type' property.");
+ }
+
+ Component.prototype.updateBoundingBox = function()
+ {
+ //Apply global transform
+ this.transBoundingBox[0] = (this.boundingBox[0] - this.diagram.xOrigin) * this.diagram.scale;
+ this.transBoundingBox[1] = (this.boundingBox[1] - this.diagram.yOrigin) * this.diagram.scale;
+ this.transBoundingBox[2] = (this.boundingBox[2] - this.diagram.xOrigin) * this.diagram.scale;
+ this.transBoundingBox[3] = (this.boundingBox[3] - this.diagram.yOrigin) * this.diagram.scale;
+ //this.getMiddle();
+ this.label.x = this.transBoundingBox[0]- 5;
+ this.label.y = (this.transBoundingBox[3] - this.transBoundingBox[1]) / 2;
+ this.valueString.x = this.transBoundingBox[2] + 5;
+ this.valueString.y = (this.transBoundingBox[3] - this.transBoundingBox[1]) / 2;
+ }
+
+ Component.prototype.initPaint = function()
+ {
+ if(this.selectable)
+ {
+ this.ctx.strokeStyle = this.selectedColor;
+ this.ctx.fillStyle = this.selectedColor;
+ }
+ else
+ {
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ }
+ }
+
+ Component.prototype.transform = function()
+ {
+ this.ctx.translate(this.x, this.y);
+ if(this.rotation != 0)
+ this.ctx.rotate(-this.rotation);
+ }
+
+ Component.prototype.getMiddle = function()
+ {
+ this.xMiddle = (this.boundingBox[2] - this.boundingBox[0]) / 2;
+ this.yMiddle = (this.boundingBox[3] - this.boundingBox[1]) / 2;
+ }
+
+ Component.prototype.drawLabel = function()
+ {
+ if (this.label.show)
+ {
+ var textAlign;
+ this.ctx.save();
+ this.ctx.fillStyle = this.label.color;
+ this.ctx.textAlign = "left";
+ if (this.rotation == 0) //Component is vertical
+ {
+ if (this.label.position == "left") //Label is on left
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "right";
+ }
+ else if (this.label.position == "right") //Label is on right
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "left";
+ }
+ }
+ else if (this.rotation == Math.PI/2) //Component is horizontal
+ {
+ if (this.label.position == "left") //Label now on bottom
+ {
+ this.ctx.textBaseline = "top";
+ textAlign = "center";
+ }
+ else if (this.label.position == "right") //Label on top
+ {
+ this.ctx.textBaseline = "bottom";
+ textAlign = "center";
+ }
+ }
+ else if (this.rotation == Math.PI) //Component is horizontal
+ {
+ if (this.label.position == "left") //Label now on right
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "left";
+ }
+ else if (this.label.position == "right") //Label now on left
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "right";
+ }
+ }
+ else if (this.rotation == 2*Math.PI/3) //Component is vertical
+ {
+ if (this.label.position == "left") //Label is on right
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "left";
+ }
+ else if (this.label.position == "right") //Label is on right
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "right";
+ }
+ }
+ this.ctx.translate(this.label.x, this.label.y);
+ this.ctx.rotate(this.rotation);
+ this.diagram.drawSubSuperScript(this.ctx, this.label.str, 0, 0, textAlign);
+ this.ctx.restore();
+ }
+ }
+
+ Component.prototype.drawValueString = function()
+ {
+ if (this.valueString.show)
+ {
+ var textAlign;
+ this.ctx.save();
+ this.ctx.fillStyle = this.valueString.color;
+ this.ctx.textAlign = "left";
+ if (this.rotation == 0) //Component is vertical
+ {
+ if (this.valueString.position == "left") //Label is on left
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "right";
+ }
+ else if (this.valueString.position == "right") //Label is on right
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "left";
+ }
+ }
+ else if (this.rotation == Math.PI/2) //Component is horizontal
+ {
+ if (this.valueString.position == "left") //Label now on bottom
+ {
+ this.ctx.textBaseline = "top";
+ textAlign = "center";
+ }
+ else if (this.valueString.position == "right") //Label on top
+ {
+ this.ctx.textBaseline = "bottom";
+ textAlign = "center";
+ }
+ }
+ else if (this.rotation == Math.PI) //Component is horizontal
+ {
+ if (this.valueString.position == "left") //Label now on right
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "left";
+ }
+ else if (this.valueString.position == "right") //Label now on left
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "right";
+ }
+ }
+ else if (this.rotation == 2*Math.PI/3) //Component is vertical
+ {
+ if (this.valueString.position == "left") //Label is on right
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "left";
+ }
+ else if (this.valueString.position == "right") //Label is on right
+ {
+ this.ctx.textBaseline = "middle";
+ textAlign = "right";
+ }
+ }
+ this.ctx.translate(this.valueString.x, this.valueString.y);
+ this.ctx.rotate(this.rotation);
+ var str;
+ if (this.valueString.decimal < 0)
+ str = this.value + " " + this.valueString.suffix;
+ else //Force a certain number of digits
+ str = (this.value).toFixed(this.valueString.decimal) + " " + this.valueString.suffix;
+
+ this.diagram.drawSubSuperScript(this.ctx, str, 0, 0, textAlign);
+ this.ctx.restore();
+ }
+ }
+
+ Component.prototype.isInside = function(x, y)
+ {
+ var pt = transform(x, y, this.x, this.y, this.rotation);
+ if((this.transBoundingBox[0] <= pt.x) && (pt.x <= this.transBoundingBox[2]) && (this.transBoundingBox[1] <= pt.y) && (pt.y <= this.transBoundingBox[3]))
+ return true;
+ else
+ return false;
+ }
+
+ //***** NODE COMPONENT *****//
+ function Node(x, y)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-2, -2, 2, 2];
+ this.nodeRadius = 2;
+ }
+
+ copyPrototype(Node, Component);
+ Node.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawCircle(this.ctx, 0, 0, this.nodeRadius, true);
+ this.drawLabel();
+ this.ctx.restore();
+ }
+
+ Node.prototype.toString = function()
+ {
+ return "";
+ }
+
+ //***** WIRE COMPONENT *****//
+ function Wire(x1, y1, x2, y2)
+ {
+ //Call super class
+ this.Component(x1, y1);
+ this.dx = x2 - x1;
+ this.dy = y2 - y1;
+ this.boundingBox = [-5, -5, this.dx + 5, this.dy + 5];
+ }
+
+ copyPrototype(Wire, Component);
+ Wire.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawLine(this.ctx, 0, 0, this.dx, this.dy);
+ this.ctx.restore();
+ }
+
+ Wire.prototype.toString = function()
+ {
+ return "";
+ }
+
+ //***** LABEL *****//
+ function Label(x, y, value, textAlign)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-10, -10, 10, 10];
+ this.value = value;
+ this.textAlign = textAlign;
+ }
+
+ copyPrototype(Label, Component);
+ Label.prototype.paint = function()
+ {
+ this.ctx.save();
+ this.ctx.textAlign = "left";
+ this.ctx.translate(this.x, this.y);
+ this.ctx.rotate(this.rotation);
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawSubSuperScript(this.ctx, this.value, 0, 0, this.textAlign);
+ this.ctx.restore();
+ }
+
+ Label.prototype.toString = function()
+ {
+ return "";
+ }
+
+ //***** CAPACITOR COMPONENT *****//
+ function Capacitor(x, y, value)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-8, 0, 8, 48];
+ this.value = value;
+ }
+
+ copyPrototype(Capacitor, Component);
+ Capacitor.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawLine(this.ctx, 0, 0, 0, 22);
+ this.diagram.drawLine(this.ctx, -8, 22, 8, 22);
+ this.diagram.drawLine(this.ctx, -8, 26, 8, 26);
+ this.diagram.drawLine(this.ctx, 0, 26, 0, 48);
+ this.drawLabel();
+ this.drawValueString();
+ this.ctx.restore();
+ }
+
+ Capacitor.prototype.toString = function()
+ {
+ return "";
+ }
+
+ //***** RESISTOR COMPONENT *****//
+ function Resistor(x, y, value)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-5, 0, 5, 48];
+ this.value = value;
+ }
+
+ copyPrototype(Resistor, Component);
+ Resistor.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawLine(this.ctx, 0, 0, 0, 12);
+ this.diagram.drawLine(this.ctx, 0, 12, 4, 14);
+ this.diagram.drawLine(this.ctx, 4, 14, -4, 18);
+ this.diagram.drawLine(this.ctx, -4, 18, 4, 22);
+ this.diagram.drawLine(this.ctx, 4, 22, -4, 26);
+ this.diagram.drawLine(this.ctx, -4, 26, 4, 30);
+ this.diagram.drawLine(this.ctx, 4, 30, -4, 34);
+ this.diagram.drawLine(this.ctx, -4, 34, 0, 36);
+ this.diagram.drawLine(this.ctx, 0, 36, 0, 48);
+ this.drawLabel();
+ this.drawValueString();
+ this.ctx.restore();
+ }
+
+ Resistor.prototype.toString = function()
+ {
+ return "";
+ }
+
+ //***** INDUCTOR COMPONENT *****//
+ function Inductor(x, y, value)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-4, 0, 5, 48];
+ this.value = value;
+ }
+
+ copyPrototype(Inductor, Component);
+ Inductor.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawLine(this.ctx, 0, 0, 0, 14);
+ this.diagram.drawArc(this.ctx, 0, 18, 4, 6*Math.PI/4, 3*Math.PI/4);
+ this.diagram.drawArc(this.ctx, 0, 24, 4, 5*Math.PI/4, 3*Math.PI/4);
+ this.diagram.drawArc(this.ctx, 0, 30, 4, 5*Math.PI/4, 2*Math.PI/4);
+ this.diagram.drawLine(this.ctx, 0, 34, 0, 48);
+ this.drawLabel();
+ this.drawValueString();
+ this.ctx.restore();
+ }
+
+ Inductor.prototype.toString = function()
+ {
+ return "";
+ }
+
+ //***** N-CHANNEL AND P-CHANNEL MOSFET COMPONENT *****//
+ function Mosfet(x, y, value, type)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-24, 0, 8, 48];
+ this.value = value;
+ this.type = type;
+ }
+
+ copyPrototype(Mosfet, Component);
+ Mosfet.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawLine(this.ctx, 0, 0, 0, 16);
+ this.diagram.drawLine(this.ctx, -8, 16, 0, 16);
+ this.diagram.drawLine(this.ctx, -8, 16, -8, 32);
+ this.diagram.drawLine(this.ctx, -8, 32, 0, 32);
+ this.diagram.drawLine(this.ctx, 0, 32, 0, 48);
+ if (this.type == "n")
+ {
+ this.diagram.drawLine(this.ctx,-24,24,-12,24);
+ this.diagram.drawLine(this.ctx,-12,16,-12,32);
+ }
+ else if (this.type == "p")
+ {
+ this.diagram.drawLine(this.ctx, -24, 24, -16, 24);
+ this.diagram.drawCircle(this.ctx, -14, 24, 2, false);
+ this.diagram.drawLine(this.ctx, -12, 16, -12, 32);
+ }
+ this.drawLabel();
+ this.drawValueString();
+ this.ctx.restore();
+ }
+
+ Mosfet.prototype.toString = function()
+ {
+ if (this.type = "n")
+ return "";
+ else if (this.type = "p")
+ return "";
+ }
+
+ //***** VOLTAGE AND CURRENT SOURCE COMPONENT *****//
+ function Source(x, y, value, type)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-12, 0, 12, 48];
+ this.value = value;
+ this.type = type;
+ }
+
+ copyPrototype(Source, Component);
+ Source.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawLine(this.ctx, 0, 0, 0, 12);
+ this.diagram.drawCircle(this.ctx, 0, 24, 12, false);
+ this.diagram.drawLine(this.ctx, 0, 36, 0, 48);
+ if (this.type == "v")
+ {
+ //Plus sign, vertical bar
+ this.ctx.save();
+ this.ctx.translate(0, this.diagram.scale*18);
+ this.ctx.rotate(this.rotation);
+ this.diagram.drawLine(this.ctx, 0, -3, 0, 3); //this.diagram.drawLine(this.ctx, 0, 15, 0, 21);
+ this.ctx.restore();
+
+ //Plus sign, horizontal bar
+ this.ctx.save();
+ this.ctx.translate(0, this.diagram.scale*18);
+ this.ctx.rotate(this.rotation);
+ this.diagram.drawLine(this.ctx, -3, 0, 3, 0); //this.diagram.drawLine(this.ctx, -3, 18, 3, 18);
+ this.ctx.restore();
+ //Minus sign
+ this.ctx.save();
+ this.ctx.translate(0, this.diagram.scale*30);
+ this.ctx.rotate(this.rotation);
+ this.diagram.drawLine(this.ctx, -3, 0, 3, 0); //this.diagram.drawLine(this.ctx, -3, 30, 3, 30);
+ this.ctx.restore();
+ }
+ else if (this.type == "i")
+ {
+ this.diagram.drawLine(this.ctx, 0, 15, 0, 32);
+ this.diagram.drawLine(this.ctx,-3, 26, 0, 32);
+ this.diagram.drawLine(this.ctx,3, 26, 0, 32);
+ }
+ this.drawLabel();
+ this.drawValueString();
+ this.ctx.restore();
+ }
+
+ Source.prototype.toString = function()
+ {
+ if (this.type = "v")
+ return "";
+ else if (this.type = "i")
+ return "";
+ }
+
+ //***** GROUND COMPONENT *****//
+ function Ground(x, y)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-6, 0, 6, 8];
+ }
+
+ copyPrototype(Ground, Component);
+ Ground.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawLine(this.ctx, 0, 0, 0, 8);
+ this.diagram.drawLine(this.ctx, -6, 8, 6, 8);
+ this.ctx.restore();
+ }
+
+ Ground.prototype.toString = function()
+ {
+ return "";
+ }
+
+ //***** DIODE COMPONENT *****//
+ function Diode(x, y, value)
+ {
+ //Call super class
+ this.Component(x, y);
+ this.boundingBox = [-8, 0, 8, 48];
+ this.value = value;
+ }
+
+ copyPrototype(Diode, Component);
+ Diode.prototype.paint = function()
+ {
+ this.initPaint();
+ this.ctx.save();
+ this.transform();
+ this.drawLabel();
+ this.ctx.strokeStyle = this.color;
+ this.ctx.fillStyle = this.color;
+ this.diagram.drawLine(this.ctx, 0, 0, 0, 16);
+ this.diagram.drawLine(this.ctx, -8, 16, 8, 16);
+ this.diagram.drawLine(this.ctx, -8, 16, 0, 32);
+ this.diagram.drawLine(this.ctx, 8, 16, 0, 32);
+ this.diagram.drawLine(this.ctx, -8, 32, 8, 32);
+ this.diagram.drawLine(this.ctx,0 , 32, 0, 48);
+ this.ctx.restore();
+ }
+
+ Diode.prototype.toString = function()
+ {
+ return "";
+ }
+
+//////////PUBLIC FIELDS AND METHODS//////////
+ return {
+
+ Utils: Utils,
+ Color: Color,
+ Diagram: Diagram,
+ };
+}());
diff --git a/common/test/data/full/static/js/sound_labs/mosfet_amplifier.js b/common/test/data/full/static/js/sound_labs/mosfet_amplifier.js
new file mode 100644
index 0000000000..614388df27
--- /dev/null
+++ b/common/test/data/full/static/js/sound_labs/mosfet_amplifier.js
@@ -0,0 +1,658 @@
+$(document).ready(function()
+{
+ //The try catch block checks if canvas and audio libraries are present. If not, we exit and alert the user.
+ try
+ {
+ //Add corresponding listener to various UI elements
+ $('#musicTypeSelect').change(onSelectChange);
+ $('input:checkbox').click(checkboxClicked);
+ $('input:radio').click(radioButtonClicked);
+ $('#playButton').click(playButtonClicked);
+ initSound();
+ initDiagram();
+ initGraph();
+ setGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+ labEnabled = true;
+ }
+ catch(err)
+ {
+ labEnabled = false;
+ alert(err + " The tool is disabled.");
+ }
+});
+
+function initGraph()
+{
+ //Test if canvas is supported. If not, exit.
+ var testCanvas = document.createElement("canvas")
+ if (!testCanvas.getContext)
+ throw "Canvas element is not supported in this browser."
+ //Get canvas
+ var canvas = $('#graph')[0];
+ //To disable text selection outside the canvas
+ canvas.onselectstart = function(){return false;};
+ //Create an offscreen buffer
+ var buffer = document.createElement('canvas');
+ buffer.width = canvas.width;
+ buffer.height = canvas.height;
+ graph = new Plotter.Graph(50, 50, 400, 400, canvas, buffer);
+}
+
+var diagram, VS, VIn, VBias, R;
+
+function initDiagram()
+{
+ //Test if canvas is supported. If not, exit.
+ var testCanvas = document.createElement("canvas")
+ if (!testCanvas.getContext)
+ throw "Canvas element is not supported in this browser."
+
+ var element = $('#diag1');
+ diagram = new Circuit.Diagram(element, true);
+
+ //Lines
+ var wirev1 = diagram.addWire(100, 289, 100, 361);
+ var wirev2 = diagram.addWire(100, 78, 100, 135.5);
+ var wirev3 = diagram.addWire(380, 78.5, 380, 89.5);
+ var wirev4 = diagram.addWire(380, 290, 380, 361.5);
+
+ var wireh1 = diagram.addWire(100, 78, 240, 78);
+ var wireh2 = diagram.addWire(240, 243, 286, 243);
+ var wireh3 = diagram.addWire(100, 433, 240, 433);
+
+ var vOutPlus = diagram.addLabel(396, 219, "\u002B", "left");
+ var vOutLabel = diagram.addLabel(396, 244, "v_{OUT}", "left");
+ var vOutMinus = diagram.addLabel(396, 274, "\u2212", "left");
+ vOutPlus.color = Plotter.Color.lightyellow;
+ vOutLabel.color = Plotter.Color.lightyellow;
+ vOutMinus.color = Plotter.Color.lightyellow;
+
+ var vRPlus = diagram.addLabel(310, 127, "\u002B", "left");
+ var vRLabel = diagram.addLabel(310, 152, "v_{R}", "left");
+ var vRMinus = diagram.addLabel(310, 182, "\u2212", "left");
+ vRPlus.color = Plotter.Color.lightgreen;
+ vRLabel.color = Plotter.Color.lightgreen;
+ vRMinus.color = Plotter.Color.lightgreen;
+
+ //vin
+ //Plotter.Color.lightblue);
+ //vout
+ //Plotter.Color.lightyellow);
+ //vr
+ //Plotter.Color.lightgreen);
+
+ //Ground
+ var ground = diagram.addGround(240, 433);
+
+ //Resistor
+ R = diagram.addResistor(380, 99.5, 10);
+ R.label.str = "R";
+ R.valueString.suffix = "k\u03A9";
+
+ //Voltage sources
+ VS = diagram.addSource(100, 193, 1.6, "v");
+ VS.label.str = "V_{S}";
+ VS.valueString.suffix = "V";
+ VIn = diagram.addSource(240, 243, 3, "v");
+ VIn.label.str = "v_{IN}";
+ VIn.label.color = Plotter.Color.lightblue;
+ VIn.valueString.suffix = "V";
+ VIn.valueString.color = Plotter.Color.lightblue;
+ VBias = diagram.addSource(240, 338, 2.5, "v");
+ VBias.label.str = "v_{BIAS}";
+ VBias.valueString.suffix = "V";
+
+ //Mosfet
+ var nMosfet = diagram.addMosfet(380, 195, "", "n");
+
+ //diagram.showGrid = true;
+ //diagram.gridStep = 1;
+ diagram.paint();
+}
+
+function setGraph()
+{
+ var lticks = 1;
+ var sticks = 0.5;
+ //x axis
+ graph.xText = xLab;
+ graph.yText = "V_{MAX} (Volts)";
+ graph.xmin = 0;
+ graph.xmax = maxTime;
+ graph.xspan = maxTime;
+ graph.xShortTickMin = 0;
+ graph.xShortTickMax = maxTime;
+ graph.xShortTickStep = maxTime/20;
+ graph.xLongTickMin = 0;
+ graph.xLongTickMax = maxTime;
+ graph.xLongTickStep = maxTime/10;
+ graph.xLabelMin = 0;
+ graph.xLabelMax = maxTime;
+ graph.xLabelStep = maxTime/10;
+ graph.xGridMin = 0;
+ graph.xGridMax = maxTime;
+ graph.xGridStep = maxTime/10;
+ //y axis
+ graph.ymin = -maxVolt;
+ graph.ymax = maxVolt;
+ graph.yspan = 2*maxVolt;
+ graph.yShortTickMin = -maxVolt + (maxVolt % sticks);
+ graph.yShortTickMax = maxVolt - (maxVolt % sticks);
+ graph.yShortTickStep = sticks;
+ graph.yLongTickMin = -maxVolt + (maxVolt % lticks);
+ graph.yLongTickMax = maxVolt - (maxVolt % lticks);
+ graph.yLongTickStep = lticks;
+ graph.yLabelMin = -maxVolt + (maxVolt % lticks);
+ graph.yLabelMax = maxVolt - (maxVolt % lticks);
+ graph.yLabelStep = lticks;
+ graph.yGridMin = -maxVolt + (maxVolt % lticks);
+ graph.yGridMax = maxVolt - (maxVolt % lticks);
+ graph.yGridStep = lticks;
+}
+
+function generateBuffer()
+{
+ //Draw on offscreen image buffer
+ graph.paintOn("buffer");
+ graph.paint();
+}
+
+function draw()
+{
+ //Paint buffer on canvas
+ graph.paintBuffer();
+
+ //Draw on canvas
+ graph.paintOn("canvas"); //Draw on screen image
+
+ if (vinChecked)
+ graph.drawArray(time, insig, Plotter.Color.lightblue);
+ if (voutChecked)
+ graph.drawArray(time, outsig, Plotter.Color.lightyellow);
+ if (vrChecked)
+ graph.drawArray(time, rsig, Plotter.Color.lightgreen);
+}
+
+function initSound()
+{
+ sp = new Sound.Player();
+ sp.soundStarted = function()
+ {
+ $('#playButton').prop('value', "Stop");
+ }
+
+ sp.soundStopped = function()
+ {
+ $('#playButton').prop('value', "Play");
+ }
+}
+
+function communSlide()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ calculateSignals();
+ draw();
+ diagram.paint();
+ }
+}
+
+$(function()
+{
+ $("#vsSlider" ).slider({value: vS, min: 0, max: 10, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vs").html("VS = " + ui.value + " V");
+ vS = ui.value;
+ VS.value = vS;
+ communSlide();
+ }
+ });
+ $("#vs").html("VS = "+ $("#vsSlider").slider("value") + " V");
+
+ $("#vinSlider").slider({value: vIn, min: 0, max: 5, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vin").html("vIN = " + ui.value + " V");
+ vIn = ui.value;
+ VIn.value = vIn;
+ communSlide();
+ }
+ });
+ $("#vin").html("vIN = " + $("#vinSlider").slider("value") + " V");
+
+ $("#freqSlider").slider({value: freq, min: 0, max: 5000, step: 100,
+ slide: function(event, ui)
+ {
+ $("#freq").html("Frequency = " + ui.value + " Hz");
+ freq = ui.value;
+ communSlide();
+ }
+ });
+ $("#freq").html("Frequency = " + $("#freqSlider").slider("value") + " Hz");
+
+ $("#vbiasSlider").slider({value: vBias, min: 0, max: 10, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vbias").html("VBIAS = " + ui.value + " V");
+ vBias = ui.value;
+ VBias.value = vBias;
+ communSlide();
+ }
+ });
+ $("#vbias").html("VBIAS = " + $("#vbiasSlider").slider("value") + " V");
+
+ $("#rSlider").slider({value: 1, min: 0.1, max: 10, step: 0.01,
+ slide: function(event, ui)
+ {
+ //Values of slider are in Kilo Ohms
+ var val = getResistance(ui.value);
+ $(this).slider("value", val);
+ if (val >= 1.0) //kOhms
+ {
+ $("#r").html("R = " + val + " kΩ");
+ R.value = val;
+ R.valueString.suffix = "k\u03A9";
+ }
+ else
+ {
+ $("#r").html("R = " + kiloToUnit(val) + " Ω");
+ R.value = kiloToUnit(val);
+ R.valueString.suffix = "\u03A9";
+ }
+
+ r = kiloToUnit(val);
+ communSlide();
+ //return false; //Blocks keystrokes if enabled
+ }
+ });
+ $("#r").html("R = " + $("#rSlider").slider("value") + " kΩ");
+
+ $("#kSlider").slider({value: k*1000, min: 0, max: 10, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#k").html("k = " + ui.value + " mA/V2 ");
+ k = ui.value / 1000; //Values are in mA
+ communSlide();
+ }
+ });
+ $("#k").html("k = " + $("#kSlider").slider("value") + " mA/V2 ");
+
+ $("#vtSlider").slider({value: vt, min: 0, max: 10, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vt").html("VT = " + ui.value + " V");
+ vt = ui.value;
+ communSlide();
+ }
+ });
+ $("#vt").html("VT = " + $("#vtSlider").slider("value") + " V");
+
+ $("#vmaxSlider" ).slider({value: vMax, min: 1, max: 20, step: 0.1,
+ slide: function(event, ui)
+ {
+ $("#vmax").html("VMAX = " + ui.value + " V");
+ maxVolt = ui.value;
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ setGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+ }
+ }
+ });
+ $("#vmax").html("VMAX = " + $("#vmaxSlider").slider("value") + " V");
+});
+
+function getCheckboxesState()
+{
+ if($('#vinCheckbox').prop('checked'))
+ vinChecked = true;
+ else
+ vinChecked = false;
+ if($('#voutCheckbox').prop('checked'))
+ voutChecked = true;
+ else
+ voutChecked = false;
+ if($('#vrCheckbox').prop('checked'))
+ vrChecked = true;
+ else
+ vrChecked = false;
+}
+
+function getRadioButtonsState()
+{
+ if($('#vinRadioButton').prop('checked'))
+ sp.inSignal.listen = true;
+ else
+ sp.inSignal.listen = false;
+ if($('#voutRadioButton').prop('checked'))
+ sp.outSignals[0].listen = true;
+ else
+ sp.outSignals[0].listen = false;
+ if($('#vrRadioButton').prop('checked'))
+ sp.outSignals[1].listen = true;
+ else
+ sp.outSignals[1].listen = false;
+}
+
+function onSelectChange()
+{
+ if (labEnabled)
+ {
+ musicType = $("#musicTypeSelect").val();
+ sp.stopTone();
+ if (musicType == 0) //Zero Input
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 1) //Unit Impulse
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 2) //Unit Step
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ if (musicType == 3) //Sine Wave
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", false);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 4) //Square Wave
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", false);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Music
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 20; //s
+ xLab = "t (s)";
+
+ if (musicType == 5)
+ sp.load("classical.wav", musicLoaded);
+ else if (musicType == 6)
+ sp.load("folk.wav", musicLoaded);
+ else if (musicType == 7)
+ sp.load("jazz.wav", musicLoaded);
+ else
+ sp.load("reggae.wav", musicLoaded);
+ }
+ }
+}
+
+function musicLoaded()
+{
+ setGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+}
+
+function checkboxClicked()
+{
+ if (labEnabled)
+ {
+ getCheckboxesState();
+ draw();
+ }
+}
+
+function radioButtonClicked()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ getRadioButtonsState();
+ }
+}
+
+function playButtonClicked()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ else
+ sp.playTone();
+ }
+}
+
+//TO DO: PUT ALL THE FOLLOWING GLOBAL VARIABLES IN A NAMESPACE
+var labEnabled = true;
+//Graph
+var graph;
+var maxTime = 10; //In ms
+var xLab = "t (ms)";
+var maxVolt = 2;
+var time;
+var insig;
+var outsig;
+//Sound Player
+var sp;
+
+//Drop variable down for Type of Input
+var musicType = 3;
+//Checkboxes variables for Graph
+var vinChecked = true;
+var voutChecked = true;
+var vrChecked = false;
+//Slider variables
+var vS = 1.6;
+var vIn = 3.0;
+var vInMax = 5.0;
+var freq = 1000;
+var vBias = 2.5;
+var r = 10000;
+var k = 0.001;
+var vt = 1;
+var vMax = 2;
+
+function calculateSignals()
+{
+ if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
+ {
+ sp.soundLength = 1;
+ sp.sampleRate = 50000;
+ }
+ else if (musicType == 4)
+ {
+ sp.soundLength = 1;
+ sp.sampleRate = 88200;
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
+ {
+ sp.soundLength = 20;
+ sp.sampleRate = 22050;
+ }
+
+ sp.createBuffers(2); //We have two outputs, first one is the voltage across Drain, Source, the second across resistor R
+ getRadioButtonsState(); //Set what we are listening to, input, or one of the above
+
+ if (musicType == 0) //Zero Input
+ sp.generateZero();
+ else if (musicType == 1) //Unit Impulse
+ sp.generateUnitImpulse();
+ else if (musicType == 2) //Unit Step
+ sp.generateUnitStep();
+ else if (musicType == 3) //Sine Wave
+ sp.generateSineWave(vIn, freq, 0);
+ else if (musicType == 4) //Square Wave
+ sp.generateSquareWave(vIn, freq, 0);
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
+ {
+ //TO DO: MOVE OUT
+ var max = Number.NEGATIVE_INFINITY;
+ var amp = 0.0;
+
+ //Find the max and normalize
+ for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
+ {
+ amp = Math.abs(sp.audioData[i]);
+ if (amp > max)
+ max = amp;
+ }
+ max /= 0.5;
+ if (max != 0.0)
+ {
+ for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
+ {
+ sp.inSignal.data[i] = vIn*sp.audioData[i] / max;
+ }
+ }
+ else //Fill in with zeros
+ {
+ for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
+ {
+ sp.inSignal.data[i] = 0.0;
+ }
+ }
+ }
+
+ getVDS(sp.inSignal.data, sp.outSignals[0].data, vBias, vS, r, k, vt);
+ getVr(sp.outSignals[0].data, sp.outSignals[1].data);
+
+ time = [];
+ insig = [];
+ outsig = [];
+ rsig = [];
+ var i = 0;
+ var ii;
+ var imult;
+ var imax;
+ var x = 0;
+ var xinc;
+
+
+ //Scale of graph is 500 px
+ //All generated sound (sine wave etc.) except square wave have sampling rate of 50000 Hz, length 1s. We will plot the first 10 ms. That's 500 samples for 10 ms and 500 px
+ if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
+ {
+ xinc = 10/500;
+ imax = 500;
+ imult = 1;
+ }
+ else if (musicType == 4) //At 50000 Hz, square wave plays very poorly, we use 88200 Hz
+ {
+ xinc = 10/882;
+ imax = 882;
+ imult = 1;
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //All music files have a sampling rate 22050 Hz, length 20s. 20s/500px --> get value every 0.04 s ie every 882 samples.
+ {
+ xinc = 20/500;
+ imax = 500;
+ imult = 882;
+ }
+
+ while (i <= imax)
+ {
+ ii = imult*i;
+ time[i] = x;
+ insig[i] = sp.inSignal.data[ii];
+ outsig[i] = sp.outSignals[0].data[ii];
+ rsig[i] = sp.outSignals[1].data[ii];
+ x += xinc;
+ i++;
+ }
+
+ sp.normalizeAllSounds();
+}
+
+var resistance = [0.1, 0.11, 0.12, 0.13, 0.15, 0.16, 0.18, 0.2, 0.22, 0.24, 0.27, 0.3, 0.33, 0.36, 0.39, 0.43, 0.47, 0.51, 0.56, 0.62, 0.68, 0.75, 0.82, 0.91, 1, 1.1, 1.2, 1.3, 1.50, 1.6, 1.8, 2, 2.2, 2.4, 2.7, 3, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1, 10];
+
+function getResistance(value)
+{
+ var distance;
+ var minDistance = Number.POSITIVE_INFINITY;
+ var minIndex;
+
+ for (var i = 0, l = resistance.length; i < l; i++)
+ {
+ distance = Math.abs(value - resistance[i]);
+ if (distance < minDistance)
+ {
+ minDistance = distance;
+ minIndex = i;
+ }
+ }
+ return resistance[minIndex];
+}
+
+function kiloToUnit(k)
+{
+ return k*1000;
+}
+
+function getVDS(inData, outData, VBIAS, VS, R, K, VT)
+{
+ // Given vector of inputs (VGS), compute vector of outputs (VDS)
+ // VGS: input source in vector
+ // VDS: voltage across MOSFET
+ // VS: Supply Voltage
+ // R: load resistor
+ // VC: gate-to-source below above which MOSFET is in saturation
+ // K, VT: mosfet parameters
+
+ var b;
+ var VC = getVC(VS, R, K, VT);
+ var indata;
+
+ for (var i = 0, l = inData.length; i < l; i++)
+ {
+ indata = inData[i] + VBIAS;
+
+ if (indata < VT)
+ outData[i] = VS;
+ else if (indata < VC)
+ outData[i] = VS - R*(K/2)*Math.pow(indata - VT, 2);
+ else
+ {
+ b = -R*K*(indata - VT) - 1;
+ outData[i] = (-b - Math.sqrt(b*b - 2*R*K*VS))/(R*K);
+ }
+ }
+};
+
+// Solve for VC, where VC is the VGS below which the MOSFET is in saturation
+function getVC(VS, R, K, VT)
+{
+ return VT + (-1 + Math.sqrt(1 + 2*VS*R*K))/(R*K);
+}
+
+function getVr(inData, outData)
+{
+ for (var i = 0, l = outData.length; i < l; i++)
+ {
+ outData[i] = vS - inData[i];
+ }
+}
diff --git a/common/test/data/full/static/js/sound_labs/plotter.js b/common/test/data/full/static/js/sound_labs/plotter.js
new file mode 100644
index 0000000000..0ce37927c5
--- /dev/null
+++ b/common/test/data/full/static/js/sound_labs/plotter.js
@@ -0,0 +1,1038 @@
+var Plotter = (function() {
+
+ //////////PRIVATE FIELDS AND METHODS//////////
+ var Utils =
+ {
+ TWO_PI: 2.0*Math.PI,
+ PI_DIV_2: Math.PI/2.0,
+
+ getxPix : function(fx, fleft, fwidth, wleft, wwidth)
+ {
+ return Math.round(wleft + wwidth * (fx - fleft) / fwidth);
+ },
+
+ getxFromPix : function(wx, wleft, wwidth, fleft, fwidth)
+ {
+ return fleft + fwidth * (wx - wleft) / wwidth;
+ },
+
+ getyPix : function(fy, fbottom, fheight, wbottom, wheight)
+ {
+ return Math.round(wbottom - wheight * (fy - fbottom) / fheight);
+ },
+
+ getyFromPix : function(wy, wbottom, wheight, fbottom, fheight)
+ {
+ return fbottom + fheight * (wbottom - wy) / wheight;
+ },
+
+ log10: function(x)
+ {
+ return Math.log(x)/Math.LN10;
+ }
+ };
+
+ var Color =
+ {
+ //Old palette
+ /*background : "rgb(0, 51, 102)", //0.0, 0.2, 0.4
+ black : "rgb(0, 0, 0)", //0.0
+ lodarkgray : "rgb(26, 26, 26)", //0.1 = 25.5
+ darkgray : "rgb(51, 51, 51)", //0.2
+ lomidgray : "rgb(102, 102, 102)", //0.4
+ midgray : "rgb(128, 128, 128)", //0.5 = 127.5
+ himidgray : "rgb(153, 153, 153)", //0.6
+ litegray : "rgb(204, 204, 204)", //0.8
+ white : "rgb(255, 255, 255)", //1.0
+
+ red : "rgb(255, 0, 0)",
+ green : "rgb(0, 255, 0)",
+ blue : "rgb(255, 255, 0)",
+ yellow : "rgb(255, 255, 0)",
+ cyan : "rgb(0, 255, 255)",
+ magenta : "rgb(255, 0, 255)",*/
+
+
+ //Solarized palette: http://ethanschoonover.com/solarized
+ base03 : "#002b36",
+ base02 : "#073642",
+ base015: "#30535c",
+ base01 : "#586e75",
+ base00 : "#657b83",
+ base0 : "#839496",
+ base1 : "#93a1a1",
+ base2 : "#eee8d5",
+ base3 : "#fdf6e3",
+ yellow : "#b58900",
+ orange : "#cb4b16",
+ red : "#dc322f",
+ magenta: "#d33682",
+ violet : "#6c71c4",
+ blue : "#268bd2",
+ cyan : "#2aa198",
+ green : "#859900",
+ //lightgreen: "#c3cd82
+ //lightblue: "#95c6e9",
+ lightblue: "#00bfff",
+ lightyellow: "#ffcf48",
+ lightgreen: "#1df914",
+ lightmagenta: "#ff3656"
+ };
+
+ ////////// GENERAL DRAWING ROUTINES //////////
+
+ function drawLine(c, x1, y1, x2, y2)
+ {
+ c.beginPath();
+ c.moveTo(x1 + 0.5, y1 + 0.5);
+ c.lineTo(x2 + 0.5, y2 + 0.5);
+ c.stroke();
+ }
+
+ //Draws a rectangle, top left corner x1, y1 and bottom right corner x2, y2
+ function drawRect(c, x1, y1, x2, y2)
+ {
+ c.strokeRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0);
+ }
+
+ function fillRect(c, x1, y1, x2, y2)
+ {
+ c.fillRect(x1, y1, x2 - x1 + 1.0, y2 - y1 + 1.0);
+ }
+
+ function clearRect(c, x1, y1, x2, y2)
+ {
+ c.clearRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0);
+ }
+
+ function drawPixel(c, x, y)
+ {
+ c.fillRect(x, y, 1.0, 1.0);
+ }
+
+ function drawPoint(c, x, y, radius)
+ {
+ c.beginPath();
+ c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise
+ c.fill();
+ }
+
+ function drawHollowPoint(c, x, y, radius)
+ {
+ c.beginPath();
+ c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise
+ c.stroke();
+ }
+
+ function drawTriangle(c, x1, y1, x2, y2, x3, y3)
+ {
+ c.beginPath();
+ c.moveTo(x1 + 0.5, y1 + 0.5);
+ c.lineTo(x2 + 0.5, y2 + 0.5);
+ c.lineTo(x3 + 0.5, y3 + 0.5);
+ c.closePath();
+ c.stroke();
+ }
+
+ function fillTriangle(c, x1, y1, x2, y2, x3, y3)
+ {
+ c.beginPath();
+ c.moveTo(x1 + 0.5, y1 + 0.5);
+ c.lineTo(x2 + 0.5, y2 + 0.5);
+ c.lineTo(x3 + 0.5, y3 + 0.5);
+ c.closePath();
+ c.fill();
+ }
+
+ function drawHalfCircle(c, x, y, radius, concaveDown) //For inductance only
+ {
+ c.beginPath();
+ if (concaveDown)
+ c.arc(x + 0.5, y + 0.5, radius, 0, Math.PI, true); //Last param is anticlockwise
+ else
+ c.arc(x + 0.5, y + 0.5, radius, Math.PI, 0, true); //Last param is anticlockwise
+ c.stroke();
+ }
+
+ function drawDiamond(c, x, y, h)
+ {
+ var xc = x + 0.5;
+ var yc = y + 0.5;
+
+ c.beginPath();
+ c.moveTo(xc-h, yc);
+ c.lineTo(xc, yc-h);
+ c.lineTo(xc+h, yc);
+ c.lineTo(xc, yc+h);
+ c.closePath();
+
+ c.fill();
+ }
+
+ function drawX(c, x, y, h)
+ {
+ var xc = x + 0.5;
+ var yc = y + 0.5;
+
+ c.beginPath();
+ c.moveTo(xc+h, yc-h);
+ c.lineTo(xc-h, yc+h);
+ c.moveTo(xc-h, yc-h);
+ c.lineTo(xc+h, yc+h);
+ c.stroke();
+ }
+
+ function drawArrow(c, x1, y1, x2, y2, base, height)
+ {
+ var xs1 = x1 + 0.5;
+ var ys1 = y1 + 0.5;
+ var xs2 = x2 + 0.5;
+ var ys2 = y2 + 0.5;
+ var xv = x2 - x1;
+ var yv = y2 - y1;
+ var ang = Math.atan2(-yv, xv);
+
+ c.beginPath();
+ //Arrow line
+ c.moveTo(xs1, ys1);
+ c.lineTo(xs2, ys2);
+ c.stroke();
+ //Arrow head, first draw a triangle with top on origin then translate/rotate to orient and fit on line
+ c.save();
+ c.beginPath();
+ c.translate(xs2, ys2);
+ c.rotate(Utils.PI_DIV_2-ang);
+
+ c.moveTo(0, 0);
+ c.lineTo(-base, height);
+ c.lineTo(base, height);
+ c.closePath();
+ c.fill();
+ //c.stroke();
+ c.restore();
+ }
+
+ function DrawingZone(left, top, width, height)
+ {
+ this.left = left;
+ this.top = top;
+ this.width = width;
+ this.height = height;
+ this.right = left + width - 1;
+ this.bottom = top + height - 1;
+ }
+
+ function Graph(x, y, width, height, canvas, buffer)
+ {
+ this.canvas = canvas;
+ this.buffer = buffer;
+ this.canvas_ctx = canvas.getContext("2d");
+ this.buffer_ctx = buffer.getContext("2d");
+ this.canvasColor = Color.base02; //Color.background : "rgb(0, 51, 102)"
+
+ //Use the screen canvas
+ this.ctx = this.canvas_ctx;
+
+ this.drawingZone = new DrawingZone(x, y, width, height);
+ this.drawingZoneColor = Color.base03; //Color.black;
+ this.drawingZoneBorderColor = Color.base01; //Color.lomidgray;
+
+ this.xGridColor = Color.base015; //Color.darkGray;
+ this.xAxisColor = Color.base00; //Color.himidgray;
+ this.xLabelColor = Color.base1; //Color.himidgray;
+ this.xTextColor = Color.base2; //Color.litegray;
+
+ this.yGridColor = Color.base015; //Color.darkGray;
+ this.yAxisColor = Color.base00; //Color.himidgray;
+ this.yLabelColor = Color.base1; //Color.himidgray;
+ this.yTextColor = Color.base2; //Color.litegray;
+
+ this.xText = "x";
+ this.yText = "y";
+
+ this.xmin = -1.0;
+ this.xmax = 1.0;
+ this.xspan = 2.0;
+ this.ymin = -10.0;
+ this.ymax = 10.0;
+ this.yspan = 20.0;
+
+ this.x0 = 0.0;
+ this.y0 = 0.0;
+ this.wx0 = 0;
+ this.wy0 = 0;
+ this.xShortTickStep = 0.1;
+ this.xShortTickMin = this.xmin;
+ this.xShortTickMax = this.xmax;
+
+ this.xLongTickStep = 0.2;
+ this.xLongTickMin = this.xmin;
+ this.xLongTickMax = this.xmax;
+
+ this.xLabelStep = 0.2;
+ this.xLabelMin = this.xmin;
+ this.xLabelMax = this.xmax;
+
+ this.xGridStep = 0.2;
+ this.xGridMin = this.xmin;
+ this.xGridMax = this.xmax;
+
+ this.formatxzero = true;
+ this.formatyzero = true;
+
+ this.yShortTickStep = 1;
+ this.yShortTickMin = this.ymin;
+ this.yShortTickMax = this.ymax;
+
+ this.yLongTickStep = 2;
+ this.yLongTickMin = this.ymin;
+ this.yLongTickMax = this.ymax;
+
+ this.yLabelStep = 2;
+ this.yLabelMin = this.ymin;
+ this.yLabelMax = this.ymax;
+
+ this.yGridStep = 2;
+ this.yGridMin = this.ymin;
+ this.yGridMax = this.ymax;
+
+ this.automaticxLabels = true;
+ this.xLabelyOffset = 7;
+ this.automaticyLabels = true;
+ this.yLabelxOffset = -7;
+
+ this.xTextxOffset = 9;
+ this.yTextyOffset = -9;
+
+ this.hasxLog = false;
+ this.hasyLog = false;
+ this.xPowerMin = 1;
+ this.xPowerMax = 5;
+ this.yPowerMin = 1;
+ this.yPowerMax = 5;
+ this.xLabelDecimalDigits = 1;
+ this.yLabelDecimalDigits = 1;
+
+ this.showxGrid = true;
+ this.showyGrid = true;
+ this.showBorder = true;
+ this.showxShortTicks = true;
+ this.showxLongTicks = true;
+ this.showxLabels = true;
+ this.showyShortTicks = true;
+ this.showyLongTicks = true;
+ this.showyLabels = true;
+ this.showxAxis = true;
+ this.showxText = true;
+ this.showyAxis = true;
+ this.showyText = true;
+
+ this.paintOn = function(where) //On what context the drawing commands will operate
+ {
+ if (where == "buffer")
+ this.ctx = this.buffer_ctx;
+ else if (where == "canvas")
+ this.ctx = this.canvas_ctx; //Default behavior
+ };
+
+ this.paintBuffer = function() //Paints buffer on screen canvas
+ {
+ this.canvas_ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ this.canvas_ctx.drawImage(buffer, 0, 0);
+ };
+
+ this.paintCanvas = function() //Paints screen canvas on buffer
+ {
+ this.buffer_ctx.clearRect(0, 0, this.buffer.width, this.buffer.height);
+ this.buffer_ctx.drawImage(canvas, 0, 0);
+ };
+
+ this.drawBorder = function()
+ {
+ this.ctx.strokeStyle = this.drawingZoneBorderColor;
+ drawRect(this.ctx, this.drawingZone.left, this.drawingZone.top, this.drawingZone.right - 1, this.drawingZone.bottom - 1);
+ };
+
+ this.drawxAxis = function()
+ {
+ this.wy0 = this.getyPix(this.y0);
+ this.ctx.strokeStyle = this.xAxisColor;
+ drawLine(this.ctx, this.drawingZone.left, this.wy0, this.drawingZone.right + 6, this.wy0);
+ drawLine(this.ctx, this.drawingZone.right + 3, this.wy0 - 3, this.drawingZone.right + 3, this.wy0 + 3);
+ drawLine(this.ctx, this.drawingZone.right + 4, this.wy0 - 2, this.drawingZone.right + 4, this.wy0 + 2);
+ drawLine(this.ctx, this.drawingZone.right + 5, this.wy0 - 1, this.drawingZone.right + 5, this.wy0 + 1);
+ };
+
+ /*
+ if (this.hasxLog)
+ wx = this.getxPix(Utils.log10(x));
+ if (this.hasyLog)
+ wy = this.getyPix(Utils.log10(y));
+ */
+
+ /*
+ this.ctx.textAlign = "left";
+ this.ctx.textAlign = "center";
+ this.ctx.textAlign = "right";
+ this.ctx.textBaseline = "top";
+ this.ctx.textBaseline = "middle";
+ this.ctx.textBaseline = "bottom";
+ this.ctx.textBaseline = "alphabetic";
+ */
+
+ this.drawxLog = function()
+ {
+ var power;
+ var x;
+ var wx;
+ var wy = this.drawingZone.bottom + 12;
+ var str;
+
+ //Don't draw grid line when on border of graph
+ for(var p = this.xPowerMin; p <= this.xPowerMax; p++)
+ {
+ wx = this.getxPix(p);
+ if(wx > this.drawingZone.right)
+ wx = this.drawingZone.right;
+ //Labeled grid line
+ if (p != this.xPowerMin && p != this.xPowerMax) //Don't draw line on left or right border of graph
+ {
+ this.ctx.strokeStyle = this.xGridColor;
+ drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.top);
+ }
+ //Long ticks
+ this.ctx.strokeStyle = this.xLabelColor;
+ drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.bottom + 4);
+ //Now the labels
+ this.ctx.fillStyle = this.xLabelColor;
+ this.ctx.strokeStyle = this.xLabelColor;
+ str = "10^{" + p.toFixed(0) + "}";
+ this.drawSubSuperScript(this.ctx, str, wx, wy, "center", "top");
+
+ if (p != this.xPowerMax)
+ {
+ for(var i = 2; i < 10; i++)
+ {
+ x = p + Utils.log10(i);
+ wx = this.getxPix(x);
+ //Grid
+ this.ctx.strokeStyle = this.xGridColor;
+ drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.top);
+ //Short ticks
+ this.ctx.strokeStyle = this.xLabelColor;
+ drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.bottom + 2);
+ }
+ }
+ }
+ }
+
+ this.drawyLog = function()
+ {
+ var power;
+ var y;
+ var wy;
+ var wx = this.drawingZone.left - 7;
+ var str;
+
+ //Don't draw grid line when on border of graph
+ for(var p = this.yPowerMin; p <= this.yPowerMax; p++)
+ {
+ wy = this.getyPix(p);
+ if(wy < this.drawingZone.top)
+ wy = this.drawingZone.top;
+ //Labeled grid line
+ if (p != this.yPowerMin && p != this.yPowerMax) //Don't draw line on left or right border of graph
+ {
+ this.ctx.strokeStyle = this.yGridColor;
+ drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.right, wy);
+ }
+ //Long ticks
+ this.ctx.strokeStyle = this.yLabelColor;
+ drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.left - 4, wy);
+ //Now the labels
+ this.ctx.fillStyle = this.yLabelColor;
+ this.ctx.strokeStyle = this.yLabelColor;
+ str = "10^{" + p.toFixed(0) + "}";
+ this.drawSubSuperScript(this.ctx, str, wx, wy, "right", "middle");
+
+ if (p != this.xPowerMax)
+ {
+ for(var i = 2; i < 10; i++)
+ {
+ y = p + Utils.log10(i);
+ wy = this.getyPix(y);
+ //Grid
+ this.ctx.strokeStyle = this.yGridColor;
+ drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.right, wy);
+ //Short ticks
+ this.ctx.strokeStyle = this.xLabelColor;
+ drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.left - 2, wy);
+ }
+ }
+ }
+ }
+
+ this.drawxGrid = function()
+ {
+ var x;
+ var wx;
+
+ this.ctx.strokeStyle = this.xGridColor;
+
+ if(this.xGridStep > 0)
+ {
+ for(x = this.xGridMin; x <= this.xGridMax; x += this.xGridStep)
+ {
+ wx = this.getxPix(x);
+ if(wx > this.drawingZone.right)
+ wx = this.drawingZone.right;
+ drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.top);
+ }
+ }
+ };
+
+ this.drawxLongTicks = function()
+ {
+ var x;
+ var wx;
+
+ this.ctx.strokeStyle = this.xLabelColor;
+
+ if(this.xLongTickStep > 0)
+ {
+ for(x = this.xLongTickMin; x <= this.xLongTickMax; x += this.xLongTickStep)
+ {
+ wx = this.getxPix(x);
+ if(wx > this.drawingZone.right)
+ wx = this.drawingZone.right;
+ drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.bottom + 4);
+ }
+ }
+ };
+
+ this.drawxShortTicks = function()
+ {
+ var x;
+ var wx;
+
+ this.ctx.strokeStyle = this.xLabelColor;
+
+ if(this.xShortTickStep > 0)
+ {
+ for(x = this.xShortTickMin; x <= this.xShortTickMax; x += this.xShortTickStep)
+ {
+ wx = this.getxPix(x);
+ if(wx > this.drawingZone.right)
+ wx = this.drawingZone.right;
+ drawLine(this.ctx, wx, this.drawingZone.bottom, wx, this.drawingZone.bottom + 2);
+ }
+ }
+ };
+
+ this.drawyAxis = function()
+ {
+ this.wx0 = this.getxPix(this.x0);
+
+ this.ctx.strokeStyle = this.yAxisColor;
+ drawLine(this.ctx, this.wx0, this.drawingZone.bottom, this.wx0, this.drawingZone.top - 6);
+ drawLine(this.ctx, this.wx0 - 3, this.drawingZone.top - 3, this.wx0 + 3, this.drawingZone.top - 3);
+ drawLine(this.ctx, this.wx0 - 2, this.drawingZone.top - 4, this.wx0 + 2, this.drawingZone.top - 4);
+ drawLine(this.ctx, this.wx0 - 1, this.drawingZone.top - 5, this.wx0 + 1, this.drawingZone.top - 5);
+ };
+
+ this.drawyLongTicks = function()
+ {
+ var y;
+ var wy;
+
+ this.ctx.strokeStyle = this.yLabelColor;
+
+ if(this.yLongTickStep > 0)
+ {
+ for(y = this.yLongTickMin; y <= this.yLongTickMax; y += this.yLongTickStep)
+ {
+ wy = this.getyPix(y);
+ if(wy < this.drawingZone.top)
+ wy = this.drawingZone.top;
+ drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.left - 4, wy);
+ }
+ }
+ };
+
+ this.drawyShortTicks = function()
+ {
+ var y;
+ var wy;
+
+ this.ctx.strokeStyle = this.yLabelColor;
+
+ if(this.yShortTickStep > 0)
+ {
+ for(y = this.yShortTickMin; y <= this.yShortTickMax; y += this.yShortTickStep)
+ {
+ wy = this.getyPix(y);
+ if(wy < this.drawingZone.top)
+ wy = this.drawingZone.top;
+ drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.left - 2, wy);
+ }
+ }
+ };
+
+ this.drawyGrid = function()
+ {
+ var y;
+ var wy;
+
+ this.ctx.strokeStyle = this.yGridColor;
+
+ if(this.yGridStep > 0)
+ {
+ for(y = this.yGridMin; y <= this.yGridMax; y += this.yGridStep)
+ {
+ wy = this.getyPix(y);
+ if(wy < this.drawingZone.top)
+ wy = this.drawingZone.top;
+ drawLine(this.ctx, this.drawingZone.left, wy, this.drawingZone.right, wy);
+ }
+ }
+ };
+
+ this.drawxLabels = function()
+ {
+ var x;
+ var wx = 0;
+ var wy = this.drawingZone.bottom + this.xLabelyOffset;
+ //y coordinate of all labels
+ var str;
+
+ this.ctx.font = "8pt Verdana bold";
+ this.ctx.fillStyle = this.xLabelColor;
+ this.ctx.strokeStyle = this.xLabelColor;
+ this.ctx.textAlign = "center";
+ this.ctx.textBaseline = "top";
+
+ if(this.automaticxLabels)
+ {
+ for( x = this.xLabelMin; x <= this.xLabelMax; x += this.xLabelStep)
+ {
+ wx = this.getxPix(x);
+
+ if(Math.abs(x) < 0.00001 && this.formatxzero)
+ str = "0";
+ else
+ str = x.toFixed(this.xLabelDecimalDigits);
+
+ //this.ctx.fillText(this.text, xmid, ymid);
+ this.ctx.strokeText(str, wx, wy);
+ this.ctx.fillText(str, wx, wy);
+ }
+ }
+ }
+
+ this.drawxText = function()
+ {
+ var x;
+ var wx = this.drawingZone.right + this.xTextxOffset;
+ var wy = this.getyPix(this.y0);
+
+ this.ctx.fillStyle = this.xTextColor;
+ this.ctx.strokeStyle = this.xTextColor;
+ this.drawSubSuperScript(this.ctx, this.xText, wx, wy, "left", "middle", "10pt Verdana bold", "8pt Verdana bold");
+ };
+
+ this.drawyLabels = function()
+ {
+ var y;
+ var wy = 0;
+ var wx = this.drawingZone.left + this.yLabelxOffset;
+ var str;
+
+ this.ctx.font = "8pt Verdana bold";
+ this.ctx.fillStyle = this.yLabelColor;
+ this.ctx.strokeStyle = this.yLabelColor;
+ this.ctx.textAlign = "right";
+ this.ctx.textBaseline = "middle";
+
+ if(this.automaticyLabels)
+ {
+ for( y = this.yLabelMin; y <= this.yLabelMax; y += this.yLabelStep)
+ {
+ wy = this.getyPix(y);
+
+ if(Math.abs(y) < 0.00001 && this.formatyzero)
+ str = "0";
+ else
+ str = y.toFixed(this.yLabelDecimalDigits);
+
+ this.ctx.strokeText(str, wx, wy);
+ this.ctx.fillText(str, wx, wy);
+ }
+ }
+ };
+
+ this.drawyText = function()
+ {
+ var x;
+ var wx = this.getxPix(this.x0);
+ var wy = this.drawingZone.top + this.yTextyOffset;
+
+ this.ctx.fillStyle = this.yTextColor;
+ this.ctx.strokeStyle = this.yTextColor;
+ this.drawSubSuperScript(this.ctx, this.yText, wx, wy, "left", "bottom", "10pt Verdana bold", "8pt Verdana bold");
+ };
+
+ this.parseSubSuperScriptText = function(str)
+ {
+ /*var regExpSub = /_\{(.*?)\}/g;
+ var regExpSup = /\^\{(.*?)\}/g;
+ var subs = [];
+ var sups = [];
+ var text = [];
+ var finalText = [];
+ var isSub = false;
+ var isSup = false;
+
+ subs = str.match(regExpSub);
+ for (var i = 0; i < subs.length; i++)
+ {
+ subs[i] = subs[i].substring(2, subs[i].length - 1); //Discard _{ and }
+ }
+
+ sups = str.match(regExpSup);
+ for (var i = 0; i < sups.length; i++)
+ {
+ sups[i] = sups[i].substring(2, sups[i].length - 1); //Discard ^{ and }
+ }*/
+
+ var len = str.length;
+ var i = 0;
+ var start;
+ var end;
+ found = false;
+ var text = [];
+ var type;
+ var ntext = "";
+
+ while (i < len)
+ {
+ if (str[i] == "_") //Encountered a potential subscript _
+ type = "sub";
+ else if (str[i] == "^") //Encountered a potential superscript ^
+ type = "sup";
+
+ if (type == "sub" || type == "sup")
+ {
+ if (str[i+1] == "{")
+ {
+ i += 2; //Discard _{ or ^{
+ start = i;
+ found = false;
+ while (i < len) //Look for }
+ {
+ if (str[i] == "}")
+ {
+ found = true;
+ end = i;
+ break;
+ }
+ i++;
+ }
+ if (found && end > start) //Discard empty subscript ie _{}
+ {
+ //Store previous normal text if not empty and tag it as so
+ if (ntext.length != 0)
+ {
+ text.push({s: ntext, type: "normal"});
+ ntext = "";
+ }
+ //Store subscript or superscript and tag it as so
+ if (type == "sub")
+ text.push({s: str.substring(start, end), type: "sub"});
+ else if (type == "sup")
+ text.push({s: str.substring(start, end), type: "sup"});
+ i = end + 1;
+ }
+ else
+ i = start - 2; //Nothing was found, backtrack to _ or ^
+ }
+ }
+ ntext += str[i];
+ if (i == len - 1 && ntext.length != 0) //We've reached the end, store normal text if not empty and tag it as so
+ text.push({s: ntext, type: "normal"});
+ i++;
+ }
+
+ return text;
+ }
+
+ this.subSuperScriptLength = function(c, text, fNormal, fSubSup)
+ {
+ var fontNormal = fNormal;
+ var fontSubSup = fSubSup;
+
+ var xpos = 0;
+
+ for (var i = 0; i < text.length; i++)
+ {
+ if (text[i].type == "normal")
+ c.font = fontNormal;
+ else if (text[i].type == "sub")
+ c.font = fontSubSup;
+ else
+ c.font = fontSubSup;
+ xpos += c.measureText(text[i].s).width;
+ }
+
+ return xpos;
+ }
+
+ this.drawSubSuperScript = function(c, str, x, y, xway, yway, fNormal, fSubSup)
+ {
+ var fontNormal = (typeof fNormal == 'undefined') ? "8pt Verdana bold" : fNormal;
+ var fontSubSup = (typeof fSubSup == 'undefined') ? "7pt Verdana bold" : fSubSup;
+
+ this.ctx.textAlign = "left";
+ this.ctx.textBaseline = yway;
+
+ var text = this.parseSubSuperScriptText(str);
+ var len = this.subSuperScriptLength(c, text, fontNormal, fontSubSup);
+ var xposIni = x;
+ var yposIni = y;
+ var xpos, ypos;
+
+ if (xway == "left")
+ xpos = xposIni;
+ else if (xway == "right")
+ xpos = xposIni - len;
+ else if (xway == "center")
+ xpos = xposIni - len/2;
+
+ //Draw the text
+ for (var i = 0; i < text.length; i++)
+ {
+ if (text[i].type == "normal")
+ {
+ c.font = fontNormal;
+ ypos = yposIni;
+ }
+ else if (text[i].type == "sub")
+ {
+ c.font = fontSubSup;
+ ypos = yposIni + 3;
+ }
+ else
+ {
+ c.font = fontSubSup;
+ ypos = yposIni - 5;
+ }
+ c.strokeText(text[i].s, xpos, ypos);
+ c.fillText(text[i].s, xpos, ypos);
+ //Advance x position
+ xpos += c.measureText(text[i].s).width + 2;
+ }
+ }
+
+ this.paint = function()
+ {
+ //Clears the canvas entirely with background color
+ this.ctx.fillStyle = this.canvasColor;
+ this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
+
+ //Clear drawing zone
+ this.ctx.fillStyle = this.drawingZoneColor;
+ fillRect(this.ctx, this.drawingZone.left, this.drawingZone.top, this.drawingZone.right, this.drawingZone.bottom);
+
+ if (!this.hasxLog)
+ {
+ if(this.showxGrid)
+ this.drawxGrid();
+ }
+
+ if (!this.hasyLog)
+ {
+ if(this.showyGrid)
+ this.drawyGrid();
+ }
+
+ if(this.showBorder)
+ this.drawBorder();
+
+ if (!this.hasxLog)
+ {
+ if(this.showxShortTicks)
+ this.drawxShortTicks();
+ if(this.showxLongTicks)
+ this.drawxLongTicks();
+ if(this.showxLabels)
+ this.drawxLabels();
+ }
+
+ if (!this.hasyLog)
+ {
+ if(this.showyShortTicks)
+ this.drawyShortTicks();
+ if(this.showyLongTicks)
+ this.drawyLongTicks();
+ if(this.showyLabels)
+ this.drawyLabels();
+ }
+
+ if (this.hasxLog)
+ this.drawxLog();
+
+ if (this.hasyLog)
+ this.drawyLog();
+
+ if(this.showxAxis)
+ this.drawxAxis();
+ if(this.showxText)
+ this.drawxText();
+
+ if(this.showyAxis)
+ this.drawyAxis();
+ if(this.showyText)
+ this.drawyText();
+
+
+ };
+
+ this.drawCurve = function(f, color)
+ {
+ var wx, wy;
+ var x, y;
+
+ this.ctx.strokeStyle = color;
+ wx = this.drawingZone.left;
+ x = this.getxFromPix(wx);
+ y = f(x);
+ wy = this.getyPix(y);
+
+ this.ctx.beginPath();
+ this.ctx.moveTo(wx + 0.5, wy + 0.5);
+
+ while(wx < this.drawingZone.right)
+ {
+ wx++;
+ x = this.getxFromPix(wx);
+ y = f(x);
+ wy = this.getyPix(y);
+ this.ctx.lineTo(wx + 0.5, wy + 0.5);
+ }
+ //this.ctx.closePath();
+
+ this.ctx.stroke();
+ };
+
+ this.drawArray = function(tt, ff, color)
+ {
+ var wx, wy;
+ var x, y;
+ var l = tt.length;
+ this.ctx.save();
+ this.ctx.beginPath();
+ this.ctx.rect(this.drawingZone.left, this.drawingZone.top, this.drawingZone.width, this.drawingZone.height);
+ this.ctx.clip();
+ this.ctx.strokeStyle = color;//"rgb(256, 0, 0)";// Color.orange; //yellow, orange, red, magenta, violet, blue, cyan, green
+
+ wx = this.getxPix(tt[0]);
+ wy = this.getyPix(ff[0]);
+ this.ctx.beginPath();
+ this.ctx.moveTo(wx + 0.5, wy + 0.5);
+
+ for (var i = 0; i < l; i++)
+ {
+ wx = this.getxPix(tt[i]);
+ wy = this.getyPix(ff[i]);
+ //this.ctx.lineTo(wx + 0.5, wy + 0.5);
+ this.ctx.lineTo(wx, wy);
+ }
+
+ //this.ctx.closePath();
+
+ this.ctx.stroke();
+ this.ctx.restore();
+ };
+
+ this.drawPoint = function(x, y, color)
+ {
+ this.ctx.fillStyle = color;
+ drawPoint(this.ctx, this.getxPix(x), this.getyPix(y), 4);
+ };
+
+ this.drawHollowPoint = function(x, y, color)
+ {
+ this.ctx.strokeStyle = color;
+ drawHollowPoint(this.ctx, this.getxPix(x), this.getyPix(y), 4);
+ };
+
+ this.drawDiamond = function(x, y, color)
+ {
+ this.ctx.fillStyle = color;
+ drawDiamond(this.ctx, this.getxPix(x), this.getyPix(y), 4);
+ };
+
+ this.drawX = function(x, y, color)
+ {
+ this.ctx.strokeStyle = color;
+ drawX(this.ctx, this.getxPix(x), this.getyPix(y), 4);
+ };
+
+ this.drawLine = function(x1, y1, x2, y2, color)
+ {
+ this.ctx.strokeStyle = color;
+ drawLine(this.ctx, this.getxPix(x1), this.getyPix(y1), this.getxPix(x2), this.getyPix(y2));
+ };
+
+ this.drawArrow = function(x1, y1, x2, y2, color)
+ {
+ this.ctx.strokeStyle = color;
+ this.ctx.fillStyle = color;
+ drawArrow(this.ctx, this.getxPix(x1), this.getyPix(y1), this.getxPix(x2), this.getyPix(y2), 5, 10);
+ };
+
+ this.getxPix = function(x)
+ {
+ return Math.round(this.drawingZone.left + this.drawingZone.width * (x - this.xmin) / this.xspan);
+ };
+
+ this.getyPix = function(y)
+ {
+ return Math.round(this.drawingZone.bottom - this.drawingZone.height * (y - this.ymin) / this.yspan);
+ };
+
+ this.getxFromPix = function(wx)
+ {
+ return (this.xmin + this.xspan * (wx - this.drawingZone.left) / this.drawingZone.width);
+ };
+
+ this.getyFromPix = function(wy)
+ {
+ return (this.ymin + this.yspan * (this.drawingZone.bottom - wy) / this.drawingZone.height);
+ };
+
+ this.isInside = function(x, y)
+ {
+ if((this.drawingZone.left <= x) && (x <= this.drawingZone.right) && (this.drawingZone.top <= y) && (y <= this.drawingZone.bottom))
+ return true;
+ else
+ return false;
+ };
+
+ this.inBounds = function(x, y)
+ {
+ if((this.xmin <= x) && (x <= this.xmax) && (this.ymin <= y) && (y <= this.ymax))
+ return true;
+ else
+ return false;
+ };
+ }
+
+ //////////PUBLIC FIELDS AND METHODS//////////
+ return {
+
+ Utils: Utils,
+ Color: Color,
+ DrawingZone: DrawingZone,
+ Graph: Graph,
+ };
+}());
diff --git a/common/test/data/full/static/js/sound_labs/rc_filters.js b/common/test/data/full/static/js/sound_labs/rc_filters.js
new file mode 100644
index 0000000000..caa3781b4a
--- /dev/null
+++ b/common/test/data/full/static/js/sound_labs/rc_filters.js
@@ -0,0 +1,938 @@
+$(document).ready(function()
+{
+ //The try catch block checks if canvas and audio libraries are present. If not, we exit and alert the user.
+ try
+ {
+ //Add corresponding listener to various UI elements
+ $('#musicTypeSelect').change(onSelectChange);
+ $('input:checkbox').click(checkboxClicked);
+ $('input:radio').click(radioButtonClicked);
+ $('#playButton').click(playButtonClicked);
+ initSound();
+ initDiagram();
+ initGraphs();
+ setTimeGraph();
+ setMagGraph();
+ setPhaseGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+ labEnabled = true;
+ $("#graphTabs").tabs();
+ $("#graphTabs").bind("tabsselect", tabSelected);
+ }
+ catch(err)
+ {
+ labEnabled = false;
+ alert(err + " The tool is disabled.");
+ }
+});
+
+function initGraphs()
+{
+ //Test if canvas is supported. If not, exit.
+ var testCanvas = document.createElement("canvas")
+ if (!testCanvas.getContext)
+ throw "Canvas element is not supported in this browser."
+
+ //Time graph
+ //Get canvas
+ var timeCanvas = $('#time')[0];
+ //To disable text selection outside the canvas
+ timeCanvas.onselectstart = function(){return false;};
+ //Create an offscreen buffer
+ var timeBuffer = document.createElement('canvas');
+ timeBuffer.width = timeCanvas.width;
+ timeBuffer.height = timeCanvas.height;
+ timeGraph = new Plotter.Graph(50, 50, 400, 400, timeCanvas, timeBuffer);
+
+ //Magnitude graph
+ //Get canvas
+ var magCanvas = $('#magnitude')[0];
+ //To disable text selection outside the canvas
+ magCanvas.onselectstart = function(){return false;};
+ //Create an offscreen buffer
+ var magBuffer = document.createElement('canvas');
+ magBuffer.width = magCanvas.width;
+ magBuffer.height = magCanvas.height;
+ magGraph = new Plotter.Graph(50, 50, 400, 400, magCanvas, magBuffer);
+
+ //Phase graph
+ //Get canvas
+ var phaseCanvas = $('#phase')[0];
+ //To disable text selection outside the canvas
+ phaseCanvas.onselectstart = function(){return false;};
+ //Create an offscreen buffer
+ var phaseBuffer = document.createElement('canvas');
+ phaseBuffer.width = phaseCanvas.width;
+ phaseBuffer.height = phaseCanvas.height;
+ phaseGraph = new Plotter.Graph(50, 50, 400, 400, phaseCanvas, phaseBuffer);
+}
+
+var diagram, VIn, R, C;
+
+function initDiagram()
+{
+ //Test if canvas is supported. If not, exit.
+ var testCanvas = document.createElement("canvas")
+ if (!testCanvas.getContext)
+ throw "Canvas element is not supported in this browser."
+
+ var element = $('#diag2');
+ diagram = new Circuit.Diagram(element, true);
+
+ //Lines
+ var wirev1 = diagram.addWire(100, 295, 100, 325);
+ var wirev2 = diagram.addWire(100, 140, 100, 170);
+ var wirev3 = diagram.addWire(380, 295, 380, 325);
+ var wirev4 = diagram.addWire(380, 140, 380, 170);
+ var wireh1 = diagram.addWire(100, 140, 145, 140);
+ var wireh2 = diagram.addWire(285, 140, 333, 140);
+ var wireh3 = diagram.addWire(100, 355, 240, 355);
+
+ var rLabel = diagram.addLabel(205, 75, "\u002B v_{R} \u2212", "left");
+ var cLabelPlus = diagram.addLabel(305, 225, "\u002B", "left");
+ var cLabel = diagram.addLabel(305, 250, "v_{C}", "left");
+ var cLabelMinus = diagram.addLabel(305, 270, "\u2212", "left");
+ rLabel.color = Plotter.Color.lightgreen;
+ cLabelPlus.color = Plotter.Color.lightyellow;
+ cLabel.color = Plotter.Color.lightyellow;
+ cLabelMinus.color = Plotter.Color.lightyellow;
+
+ //Ground
+ var ground = diagram.addGround(240, 355);
+
+ //Resistor
+ R = diagram.addResistor(190, 140, 1);
+ R.rotation = Math.PI/2;
+ R.label.str = "R";
+ R.valueString.suffix = "k\u03A9";
+
+ //Capacitor
+ C = diagram.addCapacitor(380, 200, 110);
+ C.label.str = "C";
+ C.valueString.suffix = "nF";
+
+ //Voltage source
+ VIn = diagram.addSource(100, 200, 3, "v");
+ VIn.label.str = "v_{IN}";
+ VIn.valueString.suffix = "V";
+ VIn.label.color = Plotter.Color.lightblue;
+ VIn.valueString.color = Plotter.Color.lightblue;
+
+ //diagram.showGrid = true;
+ diagram.paint();
+}
+
+function setTimeGraph()
+{
+ var lticks = 1;
+ var sticks = 0.5;
+ //x axis
+ timeGraph.xText = xLab;
+ timeGraph.yText = "V_{MAX} (Volts)";
+ timeGraph.xmin = 0;
+ timeGraph.xmax = maxTime;
+ timeGraph.xspan = maxTime;
+ timeGraph.xShortTickMin = 0;
+ timeGraph.xShortTickMax = maxTime;
+ timeGraph.xShortTickStep = maxTime/20;
+ timeGraph.xLongTickMin = 0;
+ timeGraph.xLongTickMax = maxTime;
+ timeGraph.xLongTickStep = maxTime/10;
+ timeGraph.xLabelMin = 0;
+ timeGraph.xLabelMax = maxTime;
+ timeGraph.xLabelStep = maxTime/10;
+ timeGraph.xGridMin = 0;
+ timeGraph.xGridMax = maxTime;
+ timeGraph.xGridStep = maxTime/10;
+ //y axis
+ timeGraph.ymin = -maxVolt;
+ timeGraph.ymax = maxVolt;
+ timeGraph.yspan = 2*maxVolt;
+ timeGraph.yShortTickMin = -maxVolt + (maxVolt % sticks);
+ timeGraph.yShortTickMax = maxVolt - (maxVolt % sticks);
+ timeGraph.yShortTickStep = sticks;
+ timeGraph.yLongTickMin = -maxVolt + (maxVolt % lticks);
+ timeGraph.yLongTickMax = maxVolt - (maxVolt % lticks);
+ timeGraph.yLongTickStep = lticks;
+ timeGraph.yLabelMin = -maxVolt + (maxVolt % lticks);
+ timeGraph.yLabelMax = maxVolt - (maxVolt % lticks);
+ timeGraph.yLabelStep = lticks;
+ timeGraph.yGridMin = -maxVolt + (maxVolt % lticks);
+ timeGraph.yGridMax = maxVolt - (maxVolt % lticks);
+ timeGraph.yGridStep = lticks;
+}
+
+function setMagGraph()
+{
+ var lticks = 1;
+ var sticks = 0.5;
+ //x axis
+ magGraph.xText = "f (Hz)";
+ magGraph.yText = "Magnitude (dB)";
+ magGraph.xmin = -1;
+ magGraph.xmax = 5;
+ magGraph.xspan = 6;
+ magGraph.xPowerMin = -1;
+ magGraph.xPowerMax = 5;
+
+ //y axis
+ magGraph.ymin = -100;
+ magGraph.ymax = 10;
+ magGraph.yspan = 110;
+ magGraph.yShortTickMin = -100;
+ magGraph.yShortTickMax = 10;
+ magGraph.yShortTickStep = 5;
+ magGraph.yLongTickMin = -100;
+ magGraph.yLongTickMax = 10;
+ magGraph.yLongTickStep = 10;
+ magGraph.yLabelMin = -100;
+ magGraph.yLabelMax = 10;
+ magGraph.yLabelStep = 10;
+ magGraph.yGridMin = -100;
+ magGraph.yGridMax = 10;
+ magGraph.yGridStep = 10;
+ magGraph.x0 = magGraph.xPowerMin;
+ magGraph.y0 = magGraph.ymin;
+ magGraph.hasxLog = true;
+ magGraph.hasxPowers = true;
+ magGraph.hasyLog = false;
+ magGraph.hasyPowers = false;
+}
+
+function setPhaseGraph()
+{
+ var lticks = 1;
+ var sticks = 0.5;
+ //x axis
+ phaseGraph.xText = "f (Hz)";
+ phaseGraph.yText = "Phase (degrees)";
+ phaseGraph.xmin = -1;
+ phaseGraph.xmax = 5;
+ phaseGraph.xspan = 6;
+ phaseGraph.xPowerMin = -1;
+ phaseGraph.xPowerMax = 5;
+
+ //y axis
+ phaseGraph.ymin = -100;
+ phaseGraph.ymax = 100;
+ phaseGraph.yspan = 200;
+ phaseGraph.yShortTickMin = -100;
+ phaseGraph.yShortTickMax = 100;
+ phaseGraph.yShortTickStep = 5;
+ phaseGraph.yLongTickMin = -100;
+ phaseGraph.yLongTickMax = 100;
+ phaseGraph.yLongTickStep = 10;
+ phaseGraph.yLabelMin = -100;
+ phaseGraph.yLabelMax = 100;
+ phaseGraph.yLabelStep = 10;
+ phaseGraph.yGridMin = -100;
+ phaseGraph.yGridMax = 100;
+ phaseGraph.yGridStep = 10;
+ phaseGraph.x0 = phaseGraph.xPowerMin;
+ phaseGraph.y0 = phaseGraph.ymin;
+ phaseGraph.hasxLog = true;
+ phaseGraph.hasxPowers = true;
+ phaseGraph.hasyLog = false;
+ phaseGraph.hasyPowers = false;
+}
+
+function generateBuffer()
+{
+ timeGraph.paintOn("buffer");
+ timeGraph.paint();
+ magGraph.paintOn("buffer");
+ magGraph.paint();
+ phaseGraph.paintOn("buffer");
+ phaseGraph.paint();
+}
+
+function draw()
+{
+ //Paint buffer on canvas
+ timeGraph.paintBuffer();
+
+ //Draw on canvas
+ timeGraph.paintOn("canvas"); //Draw on screen image
+
+ if (vinChecked)
+ timeGraph.drawArray(time, insig, Plotter.Color.lightblue);
+ if (vcChecked)
+ timeGraph.drawArray(time, csig, Plotter.Color.lightyellow);
+ if (vrChecked)
+ timeGraph.drawArray(time, rsig, Plotter.Color.lightgreen);
+
+ magGraph.paintBuffer();
+ magGraph.paintOn("canvas");
+ if (vcChecked)
+ magGraph.drawArray(frequencies, cmag, Plotter.Color.lightyellow);
+ if (vrChecked)
+ magGraph.drawArray(frequencies, rmag, Plotter.Color.lightgreen);
+
+ phaseGraph.paintBuffer();
+ phaseGraph.paintOn("canvas");
+ if (vcChecked)
+ phaseGraph.drawArray(frequencies, cphase, Plotter.Color.lightyellow);
+ if (vrChecked)
+ phaseGraph.drawArray(frequencies, rphase, Plotter.Color.lightgreen);
+}
+
+function initSound()
+{
+ sp = new Sound.Player();
+ sp.soundStarted = function()
+ {
+ $('#playButton').prop('value', "Stop");
+ }
+
+ sp.soundStopped = function()
+ {
+ $('#playButton').prop('value', "Play");
+ }
+}
+
+function communSlide()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ calculateSignals();
+ draw();
+ diagram.paint();
+ fc = getfCutoff(r, c);
+ $("#fc").html("fC = " + fc.toFixed(0) + " Hz");
+ }
+}
+
+$(function()
+{
+ fc = getfCutoff(r, c);
+ $("#fc").html("fC = " + fc.toFixed(0) + " Hz");
+ $("#vinSlider").slider({value: vIn, min: 0, max: 5, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vin").html("vIN = " + ui.value + " V");
+ vIn = ui.value;
+ VIn.value = vIn;
+ communSlide();
+ }
+ });
+ $("#vin").html("vIN = " + $("#vinSlider").slider("value") + " V");
+
+ $("#freqSlider").slider({value: freq, min: 100, max: 5000, step: 100,
+ slide: function(event, ui)
+ {
+ $("#freq").html("Frequency = " + ui.value + " Hz");
+ freq = ui.value;
+ communSlide();
+ }
+ });
+ $("#freq").html("Frequency = " + $("#freqSlider").slider("value") + " Hz");
+
+ $("#vbiasSlider").slider({value: vBias, min: -5, max: 5, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vbias").html("VBIAS = " + ui.value + " V");
+ vBias = ui.value;
+ communSlide();
+ }
+ });
+ $("#vbias").html("VBIAS = " + $("#vbiasSlider").slider("value") + " V");
+
+ $("#rSlider").slider({value: 1, min: 0.1, max: 10, step: 0.01,
+ slide: function(event, ui)
+ {
+ //Values of slider are in Kilo Ohms
+ var val = getResistance(ui.value);
+ $(this).slider("value", val);
+ if (val >= 1.0) //kOhms
+ {
+ $("#r").html("R = " + val + " kΩ");
+ R.value = val;
+ R.valueString.suffix = "k\u03A9";
+ }
+ else
+ {
+ $("#r").html("R = " + kiloToUnit(val) + " Ω");
+ R.value = kiloToUnit(val);
+ R.valueString.suffix = "\u03A9";
+ }
+
+ r = kiloToUnit(val);
+ communSlide();
+ //return false; //Blocks keystrokes if enabled
+ }
+ });
+ $("#r").html("R = " + $("#rSlider").slider("value") + " kΩ");
+
+ $("#vc0Slider").slider({value: vC0, min: 0, max: 5, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vc0").html("vC (0) = " + ui.value + " V");
+ vC0 = ui.value;
+ communSlide();
+ }
+ });
+ $("#vc0").html("vC (0) = " + $("#vc0Slider").slider("value") + " V");
+
+ $("#cSlider").slider({value: 110, min: 0, max: 1000, step: 1,
+ slide: function(event, ui)
+ {
+ //Values of slider are in nano Farad
+ var val = getCapacitance(ui.value);
+ $(this).slider("value", val);
+ if (val >= 1000)
+ {
+ $("#c").html("C = " + nanoToMicro(val) + " μF");
+ C.value = nanoToMicro(val);
+ C.valueString.suffix = "\u03BCF";
+ }
+ else
+ {
+ $("#c").html("C = " + val + " nF");
+ C.value = val;
+ C.valueString.suffix = "nF";
+ }
+
+ c = nanoToUnit(val);
+ communSlide();
+ //return false; //Blocks keystrokes if enabled
+ }
+ });
+ $("#c").html("C = " + $("#cSlider").slider("value") + " nF");
+ $("#vmaxSlider" ).slider({value: vMax, min: 1, max: 20, step: 0.1,
+ slide: function(event, ui)
+ {
+ $("#vmax").html("VMAX = " + ui.value + " V");
+ maxVolt = ui.value;
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ setTimeGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+ }
+ }
+ });
+ $("#vmax").html("VMAX = " + $("#vmaxSlider").slider("value") + " V");
+});
+
+function getCheckboxesState()
+{
+ if($('#vinCheckbox').prop('checked'))
+ vinChecked = true;
+ else
+ vinChecked = false;
+ if($('#vcCheckbox').prop('checked'))
+ vcChecked = true;
+ else
+ vcChecked = false;
+ if($('#vrCheckbox').prop('checked'))
+ vrChecked = true;
+ else
+ vrChecked = false;
+}
+
+function getRadioButtonsState()
+{
+ if($('#vinRadioButton').prop('checked'))
+ sp.inSignal.listen = true;
+ else
+ sp.inSignal.listen = false;
+ if($('#vcRadioButton').prop('checked'))
+ sp.outSignals[0].listen = true;
+ else
+ sp.outSignals[0].listen = false;
+ if($('#vrRadioButton').prop('checked'))
+ sp.outSignals[1].listen = true;
+ else
+ sp.outSignals[1].listen = false;
+}
+
+function onSelectChange()
+{
+ if (labEnabled)
+ {
+ musicType = $("#musicTypeSelect").val();
+ sp.stopTone();
+ if (musicType == 0) //Zero Input
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 1) //Unit Impulse
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 2) //Unit Step
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ if (musicType == 3) //Sine Wave
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", false);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 4) //Square Wave
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", false);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Music
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 20; //s
+ xLab = "t (s)";
+
+ if (musicType == 5)
+ sp.load("classical.wav", musicLoaded);
+ else if (musicType == 6)
+ sp.load("folk.wav", musicLoaded);
+ else if (musicType == 7)
+ sp.load("jazz.wav", musicLoaded);
+ else
+ sp.load("reggae.wav", musicLoaded);
+ }
+ }
+}
+
+function tabSelected(event, ui)
+{
+ if (ui.index == 0)
+ {
+ //Time, renable all sliders
+ $("#vinSlider").slider("option", "disabled", false);
+ $("#freqSlider").slider("option", "disabled", false);
+ $("#vbiasSlider").slider("option", "disabled", false);
+ $("#vc0Slider").slider("option", "disabled", false);
+ $("#vmaxSlider" ).slider("option", "disabled", false);
+ //And vinCheckbox
+ $('#vinCheckbox').attr("disabled", false);
+
+ }
+ else if (ui.index == 1 || ui.index == 2)
+ {
+ //Magnitude or phase, disable elements that have no effect on graphs
+ $("#vinSlider").slider("option", "disabled", true);
+ $("#freqSlider").slider("option", "disabled", true);
+ $("#vbiasSlider").slider("option", "disabled", true);
+ $("#vc0Slider").slider("option", "disabled", true);
+ $("#vmaxSlider" ).slider("option", "disabled", true);
+ $('#vinCheckbox').attr("disabled", true);
+ }
+
+}
+
+function musicLoaded()
+{
+ setTimeGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+}
+
+function checkboxClicked()
+{
+ if (labEnabled)
+ {
+ getCheckboxesState();
+ draw();
+ }
+}
+
+function radioButtonClicked()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ getRadioButtonsState();
+ }
+}
+
+function playButtonClicked()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ else
+ sp.playTone();
+ }
+}
+
+//TO DO: PUT ALL THE FOLLOWING GLOBAL VARIABLES IN A NAMESPACE
+var labEnabled = true;
+//Graph
+var timeGraph, magGraph, phaseGraph;
+var maxTime = 10; //In ms
+var xLab = "t (ms)";
+var maxVolt = 2;
+var time;
+var insig, csig, rsig, frequencies, cmag, rmag, cphase, rphase;
+//Sound Player
+var sp;
+
+//Drop variable down for Type of Input
+var musicType = 3;
+//Checkboxes variables for Graph
+var vinChecked = true;
+var vcChecked = true;
+var vrChecked = false;
+//Slider variables
+var vIn = 3.0;
+var vInMax = 5.0;
+var freq = 1000;
+var vBias = 0.0;
+var r = kiloToUnit(1);
+var vC0 = 0.0;
+var c = nanoToUnit(110);
+var vMax = 2;
+var fc;
+
+function calculateSignals()
+{
+ if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
+ {
+ sp.soundLength = 1;
+ sp.sampleRate = 50000;
+ }
+ else if (musicType == 4)
+ {
+ sp.soundLength = 1;
+ sp.sampleRate = 88200;
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
+ {
+ sp.soundLength = 20;
+ sp.sampleRate = 22050;
+ }
+
+ sp.createBuffers(2); //We have two outputs, first one is the voltage across capacitor C, the second across resistor R
+ getRadioButtonsState(); //Set what we are listening to, input, or one of the above
+
+ if (musicType == 0) //Zero Input
+ sp.generateZero();
+ else if (musicType == 1) //Unit Impulse
+ sp.generateUnitImpulse();
+ else if (musicType == 2) //Unit Step
+ sp.generateUnitStep();
+ else if (musicType == 3) //Sine Wave
+ sp.generateSineWave(vIn, freq, vBias);
+ else if (musicType == 4) //Square Wave
+ sp.generateSquareWave(vIn, freq, vBias);
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
+ {
+ //TO DO: MOVE OUT
+ var max = Number.NEGATIVE_INFINITY;
+ var amp = 0.0;
+
+ //Find the max and normalize
+ for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
+ {
+ amp = Math.abs(sp.audioData[i]);
+ if (amp > max)
+ max = amp;
+ }
+ max /= 0.5;
+ if (vBias != 0.0)
+ {
+ if (max != 0.0)
+ {
+ for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
+ {
+ sp.inSignal.data[i] = vBias + vIn*sp.audioData[i] / max;
+ }
+ }
+ else //Fill in with vBias
+ {
+ for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
+ {
+ sp.inSignal.data[i] = vBias;
+ }
+ }
+ }
+ else
+ {
+ if (max != 0.0)
+ {
+ for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
+ {
+ sp.inSignal.data[i] = vIn*sp.audioData[i] / max;
+ }
+ }
+ else //Fill in with zeros
+ {
+ for (var i = 0, l = sp.inSignal.data.length; i < l; i++)
+ {
+ sp.inSignal.data[i] = 0.0;
+ }
+ }
+ }
+ }
+ getVRVC(sp.inSignal.data, sp.outSignals[0].data, sp.outSignals[1].data, r, c, vC0, sp.sampleRate);
+
+ time = [];
+ insig = [];
+ csig = [];
+ rsig = [];
+ frequencies = [];
+ cmag = [];
+ rmag = [];
+ cphase = [];
+ rphase = [];
+ var i = 0;
+ var ii;
+ var imult;
+ var imax;
+ var x = 0;
+ var xinc;
+
+ //Scale of graph is 500 px
+ //All generated sound (sine wave etc.) except square wave have sampling rate of 50000 Hz, length 1s. We will plot the first 10 ms. That's 500 samples for 10 ms and 500 px
+ if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
+ {
+ xinc = 10/500;
+ imax = 500;
+ imult = 1;
+ }
+ else if (musicType == 4) //At 50000 Hz, square wave plays very poorly, we use 88200 Hz
+ {
+ xinc = 10/882;
+ imax = 882;
+ imult = 1;
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //All music files have a sampling rate 22050 Hz, length 20s. 20s/500px --> get value every 0.04 s ie every 882 samples.
+ {
+ xinc = 20/500;
+ imax = 500;
+ imult = 882;
+ }
+
+ while (i <= imax)
+ {
+ ii = imult*i;
+ time[i] = x;
+ insig[i] = sp.inSignal.data[ii];
+ csig[i] = sp.outSignals[0].data[ii];
+ rsig[i] = sp.outSignals[1].data[ii];
+ x += xinc;
+ i++;
+ }
+
+ sp.normalizeAllSounds();
+
+ //Bode plots
+ fc = getfCutoff(r, c);
+ var df = magGraph.xspan / 500; //magGraph is 500 pix large
+ var fp = magGraph.xmin;
+ var f;
+
+ //Scale of magGraph is 500 px
+ for (var i = 0; i <= 500; i++)
+ {
+ frequencies[i] = fp;
+ f = Math.pow(10, fp);
+ cmag[i] = getGainC_DB(f, fc);
+ rmag[i] = getGainR_DB(f, fc);
+ cphase[i] = getPhaseC(f, fc);
+ rphase[i] = getPhaseR(f, fc);
+ fp += df;
+ }
+}
+
+//Constants
+var TWO_PI = 2.0*Math.PI;
+var PI_DIV_2 = Math.PI/2.0;
+//var tau = R*C; //tau: Time constant
+
+var resistance = [0.1, 0.11, 0.12, 0.13, 0.15, 0.16, 0.18, 0.2, 0.22, 0.24, 0.27, 0.3, 0.33, 0.36, 0.39, 0.43, 0.47, 0.51, 0.56, 0.62, 0.68, 0.75, 0.82, 0.91, 1, 1.1, 1.2, 1.3, 1.50, 1.6, 1.8, 2, 2.2, 2.4, 2.7, 3, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1, 10];
+
+var capacitance = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910, 1000];
+
+function getResistance(value)
+{
+ var distance;
+ var minDistance = Number.POSITIVE_INFINITY;
+ var minIndex;
+
+ for (var i = 0, l = resistance.length; i < l; i++)
+ {
+ distance = Math.abs(value - resistance[i]);
+ if (distance < minDistance)
+ {
+ minDistance = distance;
+ minIndex = i;
+ }
+ }
+ return resistance[minIndex];
+}
+
+function getCapacitance(value)
+{
+ var distance;
+ var minDistance = Number.POSITIVE_INFINITY;
+ var minIndex;
+
+ for (var i = 0, l = capacitance.length; i < l; i++)
+ {
+ distance = Math.abs(value - capacitance[i]);
+ if (distance < minDistance)
+ {
+ minDistance = distance;
+ minIndex = i;
+ }
+ }
+ return capacitance[minIndex];
+}
+
+function unitToKilo(u)
+{
+ return u/1000;
+}
+
+function unitToMicro(u)
+{
+ return u*1000000;
+}
+
+function unitToNano(u)
+{
+ return u*1000000000;
+}
+
+function unitToPico(u)
+{
+ return u*1000000000000;
+}
+
+function kiloToUnit(k)
+{
+ return k*1000;
+}
+
+function microToUnit(m)
+{
+ return m/1000000;
+}
+
+function nanoToUnit(n)
+{
+ return n/1000000000;
+}
+
+function picoToUnit(p)
+{
+ return p/1000000000000;
+}
+
+function nanoToMicro(p)
+{
+ return p/1000;
+}
+
+//vIN - vOut = RC dvOUT/dt
+//xi = vIN
+//yi = vOUT
+
+//yi = alpha*x[i] + (1 - alpha)y[i-1] with alpha = dt/(RC + dt). dt is the sampling period. 0 <= alpha <= 1 is the smoothing factor. Exponentially-weighted moving average
+
+function getVRVC(inData, outData, rData, R, C, VC0, sampleRate)
+{
+ var dt = 1.0 / sampleRate;
+ var alpha = dt/(R*C + dt);
+
+ if (musicType != 1)
+ {
+ outData[0] = VC0;
+ rData[0] = inData[0] - outData[0];
+ }
+ else //Unit Impulse
+ {
+ outData[0] = inData[0];
+ rData[0] = -inData[0];
+ }
+
+ for (var i = 1, l = outData.length; i < l; i++)
+ {
+ outData[i] = outData[i-1] + alpha * (inData[i] - outData[i-1]);
+ rData[i] = inData[i] - outData[i];
+ }
+}
+
+function getfCutoff(R, C)
+{
+ return 1.0/(TWO_PI*R*C);
+}
+
+function radToDeg(angle)
+{
+ return angle*180.0/Math.PI;
+}
+
+function degToRad(angle)
+{
+ return angle*Math.PI/180.0;
+}
+
+//db for voltages: 20*log(|gain|)
+
+//LOW PASS FILTER: vC
+//Complex Gain is 1/(1+j(f/fc))
+function getGainC(f, fc)
+{
+ var frac = f/fc;
+ return 1.0/Math.sqrt(1.0 + frac*frac);
+}
+
+function getGainC_DB(f, fc)
+{
+ var frac = f/fc;
+ return -20.0*Plotter.Utils.log10(Math.sqrt(1.0 + frac*frac));
+}
+
+function getPhaseC(f, fc)
+{
+ return radToDeg(-Math.atan2(f/fc, 1.0));
+}
+
+//HIGH PASS FILTER: vR
+//Complex Gain is j(f/fc)/(1+j(f/fc))
+function getGainR(f, fc)
+{
+ var frac = f/fc;
+ return frac/Math.sqrt(1.0 + frac*frac);
+}
+
+function getGainR_DB(f, fc)
+{
+ var frac = f/fc;
+ return 20.0*(Plotter.Utils.log10(frac) - Plotter.Utils.log10(Math.sqrt(1.0 + frac*frac)));
+}
+
+function getPhaseR(f, fc)
+{
+ return radToDeg(PI_DIV_2 - Math.atan2(f/fc, 1.0));
+}
diff --git a/common/test/data/full/static/js/sound_labs/series_rlc.js b/common/test/data/full/static/js/sound_labs/series_rlc.js
new file mode 100644
index 0000000000..a5e1b02e38
--- /dev/null
+++ b/common/test/data/full/static/js/sound_labs/series_rlc.js
@@ -0,0 +1,1150 @@
+$(document).ready(function()
+{
+ //The try catch block checks if canvas and audio libraries are present. If not, we exit and alert the user.
+ try
+ {
+ //Add corresponding listener to various UI elements
+ $('#musicTypeSelect').change(onSelectChange);
+ $('input:checkbox').click(checkboxClicked);
+ $('input:radio').click(radioButtonClicked);
+ $('#playButton').click(playButtonClicked);
+ initSound();
+ initDiagram();
+ initGraphs();
+ setTimeGraph();
+ setMagGraph();
+ setPhaseGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+ labEnabled = true;
+ $("#graphTabs").tabs();
+ $("#graphTabs").bind("tabsselect", tabSelected);
+ }
+ catch(err)
+ {
+ labEnabled = false;
+ alert(err + " The tool is disabled.");
+ }
+});
+
+function initGraphs()
+{
+ //Test if canvas is supported. If not, exit.
+ var testCanvas = document.createElement("canvas")
+ if (!testCanvas.getContext)
+ throw "Canvas element is not supported in this browser."
+
+ //Time graph
+ //Get canvas
+ var timeCanvas = $('#time')[0];
+ //To disable text selection outside the canvas
+ timeCanvas.onselectstart = function(){return false;};
+ //Create an offscreen buffer
+ var timeBuffer = document.createElement('canvas');
+ timeBuffer.width = timeCanvas.width;
+ timeBuffer.height = timeCanvas.height;
+ timeGraph = new Plotter.Graph(50, 50, 400, 400, timeCanvas, timeBuffer);
+
+ //Magnitude graph
+ //Get canvas
+ var magCanvas = $('#magnitude')[0];
+ //To disable text selection outside the canvas
+ magCanvas.onselectstart = function(){return false;};
+ //Create an offscreen buffer
+ var magBuffer = document.createElement('canvas');
+ magBuffer.width = magCanvas.width;
+ magBuffer.height = magCanvas.height;
+ magGraph = new Plotter.Graph(50, 50, 400, 400, magCanvas, magBuffer);
+
+ //Phase graph
+ //Get canvas
+ var phaseCanvas = $('#phase')[0];
+ //To disable text selection outside the canvas
+ phaseCanvas.onselectstart = function(){return false;};
+ //Create an offscreen buffer
+ var phaseBuffer = document.createElement('canvas');
+ phaseBuffer.width = phaseCanvas.width;
+ phaseBuffer.height = phaseCanvas.height;
+ phaseGraph = new Plotter.Graph(50, 50, 400, 400, phaseCanvas, phaseBuffer);
+}
+
+var diagram, VIn, R, L, C;
+
+function initDiagram()
+{
+ //Test if canvas is supported. If not, exit.
+ var testCanvas = document.createElement("canvas")
+ if (!testCanvas.getContext)
+ throw "Canvas element is not supported in this browser."
+
+ var element = $('#diag3');
+ diagram = new Circuit.Diagram(element, true);
+
+ //Lines
+ var wirev1 = diagram.addWire(100, 295, 100, 325);
+ var wirev2 = diagram.addWire(100, 140, 100, 170);
+ var wirev3 = diagram.addWire(380, 295, 380, 325);
+ var wirev4 = diagram.addWire(380, 140, 380, 170);
+ var wireh1 = diagram.addWire(100, 140, 115, 140);
+ var wireh2 = diagram.addWire(225, 140, 240, 140);
+ var wireh3 = diagram.addWire(350, 140, 365, 140);
+ var wireh4 = diagram.addWire(100, 355, 240, 355);
+
+ var rLabel = diagram.addLabel(145, 75, "\u002B v_{R} \u2212", "left");
+ var lLabel = diagram.addLabel(275, 75, "\u002B v_{L} \u2212", "left");
+ var cLabelPlus = diagram.addLabel(305, 225, "\u002B", "left");
+ var cLabel = diagram.addLabel(305, 250, "v_{C}", "left");
+ var cLabelMinus = diagram.addLabel(305, 270, "\u2212", "left");
+ rLabel.color = Plotter.Color.lightgreen;
+ lLabel.color = Plotter.Color.lightmagenta;
+ cLabelPlus.color = Plotter.Color.lightyellow;
+ cLabel.color = Plotter.Color.lightyellow;
+ cLabelMinus.color = Plotter.Color.lightyellow;
+
+ //Ground
+ var ground = diagram.addGround(240, 355);
+
+ //Resistor
+ R = diagram.addResistor(130, 140, 1);
+ R.rotation = Math.PI/2;
+ R.label.str = "R";
+ R.valueString.suffix = "k\u03A9";
+
+ //Inductor
+ L = diagram.addInductor(255, 140, 10);
+ L.rotation = Math.PI/2;
+ L.label.str = "L";
+ L.valueString.suffix = "mH"
+
+ //Capacitor
+ C = diagram.addCapacitor(380, 200, 110);
+ C.label.str = "C";
+ C.valueString.suffix = "nF";
+
+ //Voltage source
+ VIn = diagram.addSource(100, 200, 3.0, "v");
+ VIn.label.str = "v_{IN}";
+ VIn.valueString.decimal = 2;
+ VIn.valueString.suffix = "V";
+ VIn.label.color = Plotter.Color.lightblue;
+ VIn.valueString.color = Plotter.Color.lightblue;
+
+ //diagram.showGrid = true;
+ diagram.paint();
+}
+
+function setTimeGraph()
+{
+ var lticks = 1;
+ var sticks = 0.5;
+ //x axis
+ timeGraph.xText = xLab;
+ timeGraph.yText = "V_{MAX} (Volts)";
+ timeGraph.xmin = 0;
+ timeGraph.xmax = maxTime;
+ timeGraph.xspan = maxTime;
+ timeGraph.xShortTickMin = 0;
+ timeGraph.xShortTickMax = maxTime;
+ timeGraph.xShortTickStep = maxTime/20;
+ timeGraph.xLongTickMin = 0;
+ timeGraph.xLongTickMax = maxTime;
+ timeGraph.xLongTickStep = maxTime/10;
+ timeGraph.xLabelMin = 0;
+ timeGraph.xLabelMax = maxTime;
+ timeGraph.xLabelStep = maxTime/10;
+ timeGraph.xGridMin = 0;
+ timeGraph.xGridMax = maxTime;
+ timeGraph.xGridStep = maxTime/10;
+ //y axis
+ timeGraph.ymin = -maxVolt;
+ timeGraph.ymax = maxVolt;
+ timeGraph.yspan = 2*maxVolt;
+ timeGraph.yShortTickMin = -maxVolt + (maxVolt % sticks);
+ timeGraph.yShortTickMax = maxVolt - (maxVolt % sticks);
+ timeGraph.yShortTickStep = sticks;
+ timeGraph.yLongTickMin = -maxVolt + (maxVolt % lticks);
+ timeGraph.yLongTickMax = maxVolt - (maxVolt % lticks);
+ timeGraph.yLongTickStep = lticks;
+ timeGraph.yLabelMin = -maxVolt + (maxVolt % lticks);
+ timeGraph.yLabelMax = maxVolt - (maxVolt % lticks);
+ timeGraph.yLabelStep = lticks;
+ timeGraph.yGridMin = -maxVolt + (maxVolt % lticks);
+ timeGraph.yGridMax = maxVolt - (maxVolt % lticks);
+ timeGraph.yGridStep = lticks;
+}
+
+function setMagGraph()
+{
+ var lticks = 1;
+ var sticks = 0.5;
+ //x axis
+ magGraph.xText = "f (Hz)";
+ magGraph.xPowerMin = -1;
+ magGraph.xPowerMax = 9;
+ magGraph.xspan = 10;
+
+ //y axis
+ magGraph.yText = "Magnitude (dB)";
+ magGraph.ymin = -300;
+ magGraph.ymax = 40;
+ magGraph.yspan = 340;
+ magGraph.yShortTickMin = -300;
+ magGraph.yShortTickMax = 40;
+ magGraph.yShortTickStep = 10;
+ magGraph.yLongTickMin = -300;
+ magGraph.yLongTickMax = 40;
+ magGraph.yLongTickStep = 20;
+ magGraph.yLabelMin = -300;
+ magGraph.yLabelMax = 40;
+ magGraph.yLabelStep = 20;
+ magGraph.yGridMin = -300;
+ magGraph.yGridMax = 40;
+ magGraph.yGridStep = 20;
+ magGraph.x0 = magGraph.xPowerMin;
+ magGraph.y0 = magGraph.ymin;
+ magGraph.hasxLog = true;
+ magGraph.hasxPowers = true;
+ magGraph.hasyLog = false;
+ magGraph.hasyPowers = false;
+ magGraph.yLabelDecimalDigits = 0;
+}
+
+function setPhaseGraph()
+{
+ var lticks = 1;
+ var sticks = 0.5;
+ //x axis
+ phaseGraph.xText = "f (Hz)";
+ phaseGraph.yText = "Phase (degrees)";
+ phaseGraph.xmin = -1;
+ phaseGraph.xmax = 5;
+ phaseGraph.xspan = 6;
+ phaseGraph.xPowerMin = -1;
+ phaseGraph.xPowerMax = 5;
+
+ //y axis
+ phaseGraph.ymin = -200;
+ phaseGraph.ymax = 200;
+ phaseGraph.yspan = 400;
+ phaseGraph.yShortTickMin = -180;
+ phaseGraph.yShortTickMax = 180;
+ phaseGraph.yShortTickStep = 10;
+ phaseGraph.yLongTickMin = -180;
+ phaseGraph.yLongTickMax = 180;
+ phaseGraph.yLongTickStep = 45;
+ phaseGraph.yLabelMin = -180;
+ phaseGraph.yLabelMax = 180;
+ phaseGraph.yLabelStep = 45;
+ phaseGraph.yGridMin = -180;
+ phaseGraph.yGridMax = 180;
+ phaseGraph.yGridStep = 10;
+ phaseGraph.x0 = phaseGraph.xPowerMin;
+ phaseGraph.y0 = phaseGraph.ymin;
+ phaseGraph.hasxLog = true;
+ phaseGraph.hasxPowers = true;
+ phaseGraph.hasyLog = false;
+ phaseGraph.hasyPowers = false;
+ phaseGraph.yLabelDecimalDigits = 0;
+}
+
+function generateBuffer()
+{
+ timeGraph.paintOn("buffer");
+ timeGraph.paint();
+ magGraph.paintOn("buffer");
+ magGraph.paint();
+ phaseGraph.paintOn("buffer");
+ phaseGraph.paint();
+}
+
+function draw()
+{
+ //Paint buffer on canvas
+ timeGraph.paintBuffer();
+
+ //Draw on canvas
+ timeGraph.paintOn("canvas"); //Draw on screen image
+
+ if (vinChecked)
+ timeGraph.drawArray(time, insig, Plotter.Color.lightblue);
+ if (vrChecked)
+ timeGraph.drawArray(time, rsig, Plotter.Color.lightgreen);
+ if (vlChecked)
+ timeGraph.drawArray(time, lsig, Plotter.Color.lightmagenta);
+ if (vcChecked)
+ timeGraph.drawArray(time, csig, Plotter.Color.lightyellow);
+
+
+ magGraph.paintBuffer();
+ magGraph.paintOn("canvas");
+ if (vrChecked)
+ magGraph.drawArray(frequencies, rmag, Plotter.Color.lightgreen);
+ if (vlChecked)
+ magGraph.drawArray(frequencies, lmag, Plotter.Color.lightmagenta);
+ if (vcChecked)
+ magGraph.drawArray(frequencies, cmag, Plotter.Color.lightyellow);
+
+ phaseGraph.paintBuffer();
+ phaseGraph.paintOn("canvas");
+ if (vrChecked)
+ phaseGraph.drawArray(frequencies, rphase, Plotter.Color.lightgreen);
+ if (vlChecked)
+ phaseGraph.drawArray(frequencies, lphase, Plotter.Color.lightmagenta);
+ if (vcChecked)
+ phaseGraph.drawArray(frequencies, cphase, Plotter.Color.lightyellow);
+}
+
+function initSound()
+{
+ sp = new Sound.Player();
+ sp.soundStarted = function()
+ {
+ $('#playButton').prop('value', "Stop");
+ }
+
+ sp.soundStopped = function()
+ {
+ $('#playButton').prop('value', "Play");
+ }
+}
+
+function communSlide()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ calculateSignals();
+ draw();
+ diagram.paint();
+ //fc = getfCutoff(r, c);
+ //$("#fc").html("fC = " + fc.toFixed(0) + " Hz");
+ }
+}
+
+$(function()
+{
+ //fc = getfCutoff(r, c);
+ //$("#fc").html("fC = " + fc.toFixed(0) + " Hz");
+ $("#vinSlider").slider({value: vIn, min: 0, max: 5, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vin").html("vIN = " + ui.value + " V");
+ vIn = ui.value;
+ VIn.value = vIn;
+ VIn.valueString.decimal = -1; //Bug?????
+ communSlide();
+ }
+ });
+ $("#vin").html("vIN = " + $("#vinSlider").slider("value") + " V");
+
+ $("#freqSlider").slider({value: freq, min: 100, max: 5000, step: 100,
+ slide: function(event, ui)
+ {
+ $("#freq").html("Frequency = " + ui.value + " Hz");
+ freq = ui.value;
+ communSlide();
+ }
+ });
+ $("#freq").html("Frequency = " + $("#freqSlider").slider("value") + " Hz");
+
+ $("#vbiasSlider").slider({value: vBias, min: -5, max: 5, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vbias").html("VBIAS = " + ui.value + " V");
+ vBias = ui.value;
+ communSlide();
+ }
+ });
+ $("#vbias").html("VBIAS = " + $("#vbiasSlider").slider("value") + " V");
+
+ $("#rSlider").slider({value: 10, min: 10, max: 1000, step: 1,
+ slide: function(event, ui)
+ {
+ //Values of slider are in Ohms
+ var val = getResistance(ui.value);
+ $(this).slider("value", val);
+ if (val >= 1000.0) //kOhms
+ {
+ $("#r").html("R = " + unitToKilo(val) + " kΩ");
+ R.value = unitToKilo(val);
+ R.valueString.suffix = "k\u03A9";
+ }
+ else
+ {
+ $("#r").html("R = " + val + " Ω");
+ R.value = val;
+ R.valueString.suffix = "\u03A9";
+ }
+
+ r = val;
+ communSlide();
+ //return false; //Blocks keystrokes
+ }
+ });
+ $("#r").html("R = " + $("#rSlider").slider("value") + " Ω");
+
+ $("#lSlider").slider({value: 10, min: 0, max: 1000, step: 1,
+ slide: function(event, ui)
+ {
+ //Values of slider are in milli Henry
+ var val = getInductance(ui.value);
+ $(this).slider("value", val);
+ if (val >= 1000.0) //H
+ {
+ $("#l").html("L = " + milliToUnit(val) + " H");
+ L.value = milliToUnit(val);
+ L.valueString.suffix = "H";
+ }
+ else
+ {
+ $("#l").html("L = " + val + " mH");
+ L.value = val;
+ L.valueString.suffix = "mH";
+ }
+
+ l = milliToUnit(val);
+ communSlide();
+ }
+ });
+ $("#l").html("L = " + $("#lSlider").slider("value") + " mH");
+
+ $("#cSlider").slider({value: 10, min: 10, max: 1000, step: 1,
+ slide: function(event, ui)
+ {
+ //Values of slider are in micro Farad
+ var val = getCapacitance(ui.value);
+ $(this).slider("value", val);
+ if (val >= 1000)
+ {
+ $("#c").html("C = " + val + " F");
+ C.value = microToUnit(val);
+ C.valueString.suffix = "F";
+ }
+ else
+ {
+ $("#c").html("C = " + val + " μF");
+ C.value = val;
+ C.valueString.suffix = "\u03BCF";
+ }
+
+ c = microToUnit(val);
+ communSlide();
+ }
+ });
+ $("#c").html("C = " + $("#cSlider").slider("value") + " μF");
+ $("#vc0Slider").slider({value: vC0, min: 0, max: 5, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#vc0").html("vC (0) = " + ui.value + " V");
+ vC0 = ui.value;
+ communSlide();
+ }
+ });
+ $("#vc0").html("vC (0) = " + $("#vc0Slider").slider("value") + " V");
+ $("#i0Slider").slider({value: i0, min: 0, max: 1, step: 0.01,
+ slide: function(event, ui)
+ {
+ $("#i0").html("i(0) = " + ui.value + " A");
+ i0 = ui.value;
+ communSlide();
+ }
+ });
+ $("#i0").html("i(0) = " + $("#i0Slider").slider("value") + " A");
+ $("#vmaxSlider" ).slider({value: vMax, min: 1, max: 20, step: 0.1,
+ slide: function(event, ui)
+ {
+ $("#vmax").html("VMAX = " + ui.value + " V");
+ maxVolt = ui.value;
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ setTimeGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+ }
+ }
+ });
+ $("#vmax").html("VMAX = " + $("#vmaxSlider").slider("value") + " V");
+});
+
+function getCheckboxesState()
+{
+ if($('#vinCheckbox').prop('checked'))
+ vinChecked = true;
+ else
+ vinChecked = false;
+ if($('#vrCheckbox').prop('checked'))
+ vrChecked = true;
+ else
+ vrChecked = false;
+ if($('#vlCheckbox').prop('checked'))
+ vlChecked = true;
+ else
+ vlChecked = false;
+ if($('#vcCheckbox').prop('checked'))
+ vcChecked = true;
+ else
+ vcChecked = false;
+}
+
+function getRadioButtonsState()
+{
+ if($('#vinRadioButton').prop('checked'))
+ sp.inSignal.listen = true;
+ else
+ sp.inSignal.listen = false;
+ if($('#vrRadioButton').prop('checked'))
+ sp.outSignals[1].listen = true;
+ else
+ sp.outSignals[1].listen = false;
+ if($('#vlRadioButton').prop('checked'))
+ sp.outSignals[2].listen = true;
+ else
+ sp.outSignals[2].listen = false;
+ if($('#vcRadioButton').prop('checked'))
+ sp.outSignals[3].listen = true;
+ else
+ sp.outSignals[3].listen = false;
+}
+
+function onSelectChange()
+{
+ if (labEnabled)
+ {
+ musicType = $("#musicTypeSelect").val();
+ sp.stopTone();
+ if (musicType == 0) //Zero Input
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 1) //Unit Impulse
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 2) //Unit Step
+ {
+ $("#vinSlider").slider( "option", "disabled", true);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ if (musicType == 3) //Sine Wave
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", false);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 4) //Square Wave
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", false);
+ maxTime = 10; //ms
+ xLab = "t (ms)";
+ musicLoaded();
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Music
+ {
+ $("#vinSlider").slider( "option", "disabled", false);
+ $("#freqSlider").slider( "option", "disabled", true);
+ maxTime = 20; //s
+ xLab = "t (s)";
+
+ if (musicType == 5)
+ sp.load("classical.wav", musicLoaded);
+ else if (musicType == 6)
+ sp.load("folk.wav", musicLoaded);
+ else if (musicType == 7)
+ sp.load("jazz.wav", musicLoaded);
+ else
+ sp.load("reggae.wav", musicLoaded);
+ }
+ }
+}
+
+function tabSelected(event, ui)
+{
+ if (ui.index == 0)
+ {
+ //Time, renable all sliders
+ $("#vinSlider").slider("option", "disabled", false);
+ $("#freqSlider").slider("option", "disabled", false);
+ $("#vbiasSlider").slider("option", "disabled", false);
+ $("#vc0Slider").slider("option", "disabled", false);
+ $("#i0Slider").slider("option", "disabled", false);
+ $("#vmaxSlider" ).slider("option", "disabled", false);
+ //And vinCheckbox
+ $('#vinCheckbox').attr("disabled", false);
+
+ }
+ else if (ui.index == 1 || ui.index == 2)
+ {
+ //Magnitude or phase, disable elements that have no effect on graphs
+ $("#vinSlider").slider("option", "disabled", true);
+ $("#freqSlider").slider("option", "disabled", true);
+ $("#vbiasSlider").slider("option", "disabled", true);
+ $("#vc0Slider").slider("option", "disabled", true);
+ $("#i0Slider").slider("option", "disabled", true);
+ $("#vmaxSlider" ).slider("option", "disabled", true);
+ $('#vinCheckbox').attr("disabled", true);
+ }
+}
+
+function musicLoaded()
+{
+ setTimeGraph();
+ generateBuffer();
+ calculateSignals();
+ draw();
+}
+
+function checkboxClicked()
+{
+ if (labEnabled)
+ {
+ getCheckboxesState();
+ draw();
+ }
+}
+
+function radioButtonClicked()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ getRadioButtonsState();
+ }
+}
+
+function playButtonClicked()
+{
+ if (labEnabled)
+ {
+ if (sp.isPlaying)
+ sp.stopTone();
+ else
+ sp.playTone();
+ }
+}
+
+//TO DO: PUT ALL THE FOLLOWING GLOBAL VARIABLES IN A NAMESPACE
+var labEnabled = true;
+//Graph
+var graph;
+var maxTime = 10; //In ms
+var xLab = "t (ms)";
+var maxVolt = 2;
+var time;
+var insig, rsig, lsig, csig, frequencies, rmag, lmag, cmag, rphase, lphase, cphase;
+
+//Sound Player
+var sp;
+
+//Drop variable down for Type of Input
+var musicType = 3;
+//Checkboxes variables for Graph
+var vinChecked = true, vrChecked = false, vlChecked = false, vcChecked = true;
+//Slider variables
+var vIn = 3.0;
+var vInMax = 5.0;
+var freq = 1000;
+var vBias = 0.0;
+var r = 10;
+var l = milliToUnit(10);
+var c = microToUnit(10);
+var vC0 = 0.0;
+var i0 = 0.0;
+var vMax = 2;
+var fc;
+
+function calculateSignals()
+{
+ if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
+ {
+ sp.soundLength = 1;
+ sp.sampleRate = 50000;
+ }
+ else if (musicType == 4)
+ {
+ sp.soundLength = 1;
+ sp.sampleRate = 88200;
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
+ {
+ sp.soundLength = 20;
+ sp.sampleRate = 22050;
+ }
+
+ //We have 4 outputs, 1: current, 2: vR, 3: vL, 4: vC
+ sp.createBuffers(4);
+
+ if (musicType == 0) //Zero Input
+ sp.generateZero();
+ else if (musicType == 1) //Unit Impulse
+ sp.generateUnitImpulse();
+ else if (musicType == 2) //Unit Step
+ sp.generateUnitStep();
+ else if (musicType == 3) //Sine Wave
+ sp.generateSineWave(vIn, freq, vBias);
+ else if (musicType == 4) //Square Wave
+ sp.generateSquareWave(vIn, freq, vBias);
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //Classical, Folk, Jazz, Reggae
+ {
+ //TO DO: MOVE OUT
+ var max = Number.NEGATIVE_INFINITY;
+ var amp = 0.0;
+
+ //Find the max and normalize
+ for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
+ {
+ amp = Math.abs(sp.audioData[i]);
+ if (amp > max)
+ max = amp;
+ }
+ max /= 0.5;
+ if (vBias != 0.0)
+ {
+ if (max != 0.0)
+ {
+ for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
+ {
+ sp.inSignal.data[i] = vBias + vIn*sp.audioData[i] / max;
+ }
+ }
+ else //Fill in with vBias
+ {
+ for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
+ {
+ sp.inSignal.data[i] = vBias;
+ }
+ }
+ }
+ else
+ {
+ if (max != 0.0)
+ {
+ for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
+ {
+ sp.inSignal.data[i] = vIn*sp.audioData[i] / max;
+ }
+ }
+ else //Fill in with zeros
+ {
+ for (var i = 0, len = sp.inSignal.data.length; i < len; i++)
+ {
+ sp.inSignal.data[i] = 0.0;
+ }
+ }
+ }
+ }
+
+ getSeriesRLC(sp.inSignal.data, sp.outSignals[0].data, sp.outSignals[1].data, sp.outSignals[2].data, sp.outSignals[3].data, r, l, c, vC0, i0, sp.sampleRate);
+
+ time = [];
+ insig = [];
+ rsig = [];
+ lsig = [];
+ csig = [];
+ frequencies = [];
+ rmag = [];
+ lmag = [];
+ cmag = [];
+ rphase = [];
+ lphase = [];
+ cphase = [];
+
+ var i = 0;
+ var ii;
+ var imult;
+ var imax;
+ var x = 0;
+ var xinc;
+
+
+ //Scale of graph is 500 px
+ //All generated sound (sine wave etc.) except square wave have sampling rate of 50000 Hz, length 1s. We will plot the first 10 ms. That's 500 samples for 10 ms and 500 px
+ if (musicType == 0 || musicType == 1 || musicType == 2 || musicType == 3)
+ {
+ xinc = 10/500;
+ imax = 500;
+ imult = 1;
+ }
+ else if (musicType == 4) //At 50000 Hz, square wave plays very poorly, we use 88200 Hz
+ {
+ xinc = 10/882;
+ imax = 882;
+ imult = 1;
+ }
+ else if (musicType == 5 || musicType == 6 || musicType == 7 || musicType == 8) //All music files have a sampling rate 22050 Hz, length 20s. 20s/500px --> get value every 0.04 s ie every 882 samples.
+ {
+ xinc = 20/500;
+ imax = 500;
+ imult = 882;
+ }
+
+ while (i <= imax)
+ {
+ ii = imult*i;
+ time[i] = x;
+ insig[i] = sp.inSignal.data[ii];
+ //MISSING I PLOT
+ rsig[i] = sp.outSignals[1].data[ii];
+ lsig[i] = sp.outSignals[2].data[ii];
+ csig[i] = sp.outSignals[3].data[ii];
+
+ x += xinc;
+ i++;
+ }
+
+ sp.normalizeAllSounds();
+
+ //Bode plots
+ var df = magGraph.xspan / 500; //magGraph is 500 pix large
+ var fp = magGraph.xmin;
+ var f;
+ var w;
+
+ //Scale of magGraph is 500 px
+ for (var i = 0; i <= 500; i++)
+ {
+ frequencies[i] = fp;
+ f = Math.pow(10, fp);
+ w = Plotter.Utils.TWO_PI*f;
+ rmag[i] = getGainR(w, r, l, c);
+ lmag[i] = getGainL(w, r, l, c);
+ cmag[i] = getGainC(w, r, l, c);
+ rphase[i] = getPhaseR(w, r, l, c);
+ lphase[i] = getPhaseL(w, r, l, c);
+ cphase[i] = getPhaseC(w, r, l, c);
+ fp += df;
+ }
+}
+
+var resistance = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910, 1000];
+
+var inductance = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 87, 91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 870, 910, 1000]; //Note: 87 and 870?
+
+var capacitance = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 91, 100, 110, 120, 130, 150, 160, 180, 200, 220, 240, 270, 300, 330, 360, 390, 430, 470, 510, 560, 620, 680, 750, 820, 910, 1000];
+
+function getResistance(value)
+{
+ var distance;
+ var minDistance = Number.POSITIVE_INFINITY;
+ var minIndex;
+
+ for (var i = 0, l = resistance.length; i < l; i++)
+ {
+ distance = Math.abs(value - resistance[i]);
+ if (distance < minDistance)
+ {
+ minDistance = distance;
+ minIndex = i;
+ }
+ }
+ return resistance[minIndex];
+}
+
+function getInductance(value)
+{
+ var distance;
+ var minDistance = Number.POSITIVE_INFINITY;
+ var minIndex;
+
+ for (var i = 0, l = inductance.length; i < l; i++)
+ {
+ distance = Math.abs(value - inductance[i]);
+ if (distance < minDistance)
+ {
+ minDistance = distance;
+ minIndex = i;
+ }
+ }
+ return inductance[minIndex];
+}
+
+function getCapacitance(value)
+{
+ var distance;
+ var minDistance = Number.POSITIVE_INFINITY;
+ var minIndex;
+
+ for (var i = 0, l = capacitance.length; i < l; i++)
+ {
+ distance = Math.abs(value - capacitance[i]);
+ if (distance < minDistance)
+ {
+ minDistance = distance;
+ minIndex = i;
+ }
+ }
+ return capacitance[minIndex];
+}
+
+function radToDeg(angle)
+{
+ return angle*180.0/Math.PI;
+}
+
+function degToRad(angle)
+{
+ return angle*Math.PI/180.0;
+}
+
+function unitToKilo(u)
+{
+ return u/1000;
+}
+
+function unitToMilli(u)
+{
+ return u*1000;
+}
+
+function unitToMicro(u)
+{
+ return u*1000000;
+}
+
+function unitToNano(u)
+{
+ return u*1000000000;
+}
+
+function unitToPico(u)
+{
+ return u*1000000000000;
+}
+
+function kiloToUnit(k)
+{
+ return k*1000;
+}
+
+function milliToUnit(m)
+{
+ return m/1000;
+}
+
+function microToUnit(m)
+{
+ return m/1000000;
+}
+
+function nanoToUnit(n)
+{
+ return n/1000000000;
+}
+
+function picoToUnit(p)
+{
+ return p/1000000000000;
+}
+
+function nanoToMicro(p)
+{
+ return p/1000;
+}
+
+/*
+ Vin = RI + LdI/dt + Vout
+ I = CdVout/dt
+
+ LCd^2Vout/dt^2 + RCdVout/dt + Vout = Vin
+
+ leads to, for x[i] array of input, y[i] array out outputs:
+
+ (dy/dt)[i] = (y[i+1] - y[i])/deltaT
+ (d^2y/dt^2)[i] = (y[i+2]-2y[i+1]+y[i])/(deltaT^2)
+
+ LC(yi+2 - 2yi+1 + yi)/dt^2 + RC(yi+1 - yi)/dt + yi = xi
+
+ yi+2 = (2.0*yi+1 - yi) - (R/L)(y[i+1]-y[i])dt + (1/LC)(x[i] - y[i])dt^2;
+
+ xi = Vin(0)
+ yi = Vc(0)
+ yi+1 = yi + dtI(0)/C
+
+ beta = [dt*dt - RCdt + LC]/C(Rdt-2L)
+*/
+
+/*NECESSARY?
+if (musicType != 1)
+ {
+ outData[0] = VC0;
+ rData[0] = inData[0] - outData[0];
+ }
+ else //Unit Impulse
+ {
+ outData[0] = inData[0];
+ rData[0] = -inData[0];
+ }
+*/
+/*
+function getVR(x, y)
+{
+ for (var i = 0, l = y.length; i < l; i++)
+ {
+ y[i] = x[i];
+ }
+}
+
+function getVL(x, y)
+{
+ for (var i = 0, l = y.length; i < l; i++)
+ {
+ y[i] = x[i];
+ }
+}
+
+function getVC(x, y, R, L, C, VC0, I0, sampleRate)
+{
+ var dt = 1.0 / sampleRate;
+ var A1 = dt*R/L;
+ var A2 = dt*dt/(L*C);
+
+ y[0] = VC0;
+ y[1] = y[0] + dt*I0/C;
+
+ for (var i = 2, l = y.length; i < l; i++)
+ {
+ y[i+2] = (2.0*y[i+1] - y[i]) - A1*(y[i+1]-y[i]) + A2*(x[i] - y[i]);
+ }
+}
+*/
+//###########################################################################
+/*
+vR + vL + vC = vIn
+Ldi/dt + Ri + q/C = vIn
+
+System of ODE, use improved Euler method.
+i = q'
+i' = (1/L)(vIn - Ri - q/C)
+
+Initial conditions given by vC(0) and i0
+
+Then
+q0 = C vC(0)
+i0
+i'0 = (1/L)(vIn - Ri0 - q0/C)
+
+qnew = qold + q'old dt = qold + i'olddt
+inew = iold + i'old dt
+i'new = (1/L)(vIn(n) - Rinew - qnew/C)
+
+qnew = qold + (q'old + q'new)dt/2 = qold + (iold + inew)dt/2
+inew = iold + (i'old + i'new)dt/2
+i'new = (1/L)(vIn(n) - Rinew - qnew/C)
+*/
+
+function getSeriesRLC(x, yi, yr, yl, yc, R, L, C, VC0, I0, sampleRate) //x input, yi, yr, yl, yc outputs
+{
+ var dt = 1.0/sampleRate;
+ var dtdiv2 = dt/2.0;
+ var cte = 1.0/L;
+ var qold = C*VC0;
+ var qnew;
+ var iold = I0;
+ var inew;
+ var diold = cte*(x[0] - R*iold - qold/C);
+ var dinew;
+ //Fill out our initial conditions on all 4 outputs
+ yc[0] = qold/C;
+ yi[0] = iold;
+ yr[0] = R*iold;
+ yl[0] = L*diold;
+
+ for (var k = 1, l = x.length; k < l; k++)
+ {
+ qnew = qold + iold*dt;
+ inew = iold + diold*dt;
+ dinew = cte*(x[k] - R*inew - qnew/C);
+ //Improved Euler method follows
+ qnew = qold + (iold + inew)*dtdiv2;
+ inew = iold + (diold + dinew)*dtdiv2;
+ dinew = cte*(x[k] - R*inew - qnew/C);
+ //Got all we need, fill up our 4 outputs
+ yc[k] = qnew/C;
+ yi[k] = inew;
+ yr[k] = R*inew;
+ yl[k] = L*dinew;
+
+ qold = qnew;
+ iold = inew;
+ diold = dinew;
+ }
+}
+
+function radToDeg(angle)
+{
+ return angle*180.0/Math.PI;
+}
+
+function degToRad(angle)
+{
+ return angle*Math.PI/180.0;
+}
+
+//db for voltages: 20*log(|gain|)
+
+//Gain and phase for vR
+//Complex Gain is R/(R +j(Lw - 1/(Cw)))
+function getGainR(w, R, L, C)
+{
+ var re = R;
+ var im = L*w - 1.0/(C*w);
+ return 20.0*(Plotter.Utils.log10(R) - Plotter.Utils.log10(Math.sqrt(re*re + im*im)));
+}
+
+function getPhaseR(w, R, L, C)
+{
+ var re = R;
+ var im = L*w - 1.0/(C*w);
+ return radToDeg(-Math.atan2(im, re));
+}
+
+//Gain and phase for vL
+//Complex Gain is jLw/(R +j(Lw - 1/(Cw)))
+function getGainL(w, R, L, C)
+{
+ var re = R;
+ var im = L*w - 1.0/(C*w);
+ return 20.0*(Plotter.Utils.log10(L*w) - Plotter.Utils.log10(Math.sqrt(re*re + im*im)));
+}
+
+function getPhaseL(w, R, L, C)
+{
+ var re = R;
+ var im = L*w - 1.0/(C*w);
+ return radToDeg(Plotter.Utils.PI_DIV_2 - Math.atan2(im, re));
+}
+
+//Gain and phase for vC
+//Complex Gain is (-j/Cw)/(R +j(Lw - 1/(Cw)))
+function getGainC(w, R, L, C)
+{
+ var re = R;
+ var im = L*w - 1.0/(C*w);
+ return 20.0*(-Plotter.Utils.log10(C*w) - Plotter.Utils.log10(Math.sqrt(re*re + im*im)));
+}
+
+function getPhaseC(w, R, L, C)
+{
+ var re = R;
+ var im = L*w - 1.0/(C*w);
+ return radToDeg(-Plotter.Utils.PI_DIV_2 - Math.atan2(im, re));
+}
diff --git a/common/test/data/full/static/js/sound_labs/sound.js b/common/test/data/full/static/js/sound_labs/sound.js
new file mode 100644
index 0000000000..a3bca38092
--- /dev/null
+++ b/common/test/data/full/static/js/sound_labs/sound.js
@@ -0,0 +1,407 @@
+var Sound = (function() {
+
+ //////////PRIVATE FIELDS AND METHODS//////////
+ var TWO_PI = 2.0*Math.PI;
+ var PI_DIV_2 = Math.PI/2.0;
+
+ function Player()
+ {
+ this.isChrome = false;
+ this.isMoz = false;
+ this.audioChrome;
+ this.audioMoz;
+ this.dir = "/static/courses/6002/sounds/";
+ this.inSignal;
+ this.outSignals = [];
+ this.numberChannels = 1;
+ this.soundLength = 1; //In seconds
+ this.sampleRate = 44100; //In Hertz
+ this.numberSamples = 44100;
+ this.isPlaying = false;
+ this.chromeTimer;
+ this.mozTimer;
+ this.audioData ;
+ this.playAudio;
+ this.outSrc;
+
+ //Test for Web Audio API --> Webkit browsers ie Chrome & Safari
+ //https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
+ if (!!window.webkitAudioContext)
+ {
+ this.audioChrome = new webkitAudioContext();
+ this.isChrome = true;
+ }
+ //Test for Audio Data API --> Firefox 4 and ulterior
+ //https://wiki.mozilla.org/Audio_Data_API
+ else if (!!new Audio().mozSetup)
+ {
+ this.audioMoz = new Audio();
+ this.isMoz = true;
+ }
+ else //Sound libraries are not supported, exit.
+ throw "Neither Web Audio API nor Audio Data API is supported in this browser.";
+
+ //To be overriden
+ this.soundStarted = function()
+ {
+ }
+
+ this.soundStopped = function()
+ {
+ }
+
+ this.load = function(url, callback)
+ {
+ var request;
+ var file = this.dir + url;
+ var self = this;
+ request = new XMLHttpRequest();
+ request.open('GET', file, true); //Asynchronous
+ request.responseType = 'arraybuffer';
+
+ request.onload = function()
+ {
+ var arrayBuffer = request.response;
+ if (arrayBuffer)
+ {
+ var audioDataTmp = new Int16Array(arrayBuffer, 44);
+ self.audioData = new Float32Array(audioDataTmp);
+ //The music has been loaded, continue execution
+ callback();
+ }
+ }
+ request.send();
+ }
+
+ this.getAudioHeader = function(audioHeaderData)
+ {
+ //44 first bytes of file are the header
+ return { // OFFS SIZE NOTES
+ chunkId : bytesToStr(audioHeaderData, 0, 4), // 0 4 "RIFF" = 0x52494646
+ chunkSize : bytesToNum(audioHeaderData, 4, 4), // 4 4 36+SubChunk2Size = 4+(8+SubChunk1Size)+(8+SubChunk2Size)
+ format : bytesToStr(audioHeaderData, 8, 4), // 8 4 "WAVE" = 0x57415645
+ subChunk1Id : bytesToStr(audioHeaderData, 12, 4), // 12 4 "fmt " = 0x666d7420
+ subChunk1Size: bytesToNum(audioHeaderData, 16, 4), // 16 4 16 for PCM
+ audioFormat : bytesToNum(audioHeaderData, 20, 2), // 20 2 PCM = 1
+ numChannels : bytesToNum(audioHeaderData, 22, 2), // 22 2 Mono = 1, Stereo = 2, etc.
+ sampleRate : bytesToNum(audioHeaderData, 24, 4), // 24 4 8000, 44100, etc
+ byteRate : bytesToNum(audioHeaderData, 28, 4), // 28 4 SampleRate*NumChannels*BitsPerSample/8
+ blockAlign : bytesToNum(audioHeaderData, 32, 2), // 32 2 NumChannels*BitsPerSample/8
+ bitsPerSample: bytesToNum(audioHeaderData, 34, 2), // 34 2 8 bits = 8, 16 bits = 16, etc...
+ subChunk2Id : bytesToStr(audioHeaderData, 36, 4), // 36 4 "data" = 0x64617461
+ subChunk2Size: bytesToNum(audioHeaderData, 40, 4) // 40 4 data size = NumSamples*NumChannels*BitsPerSample/8
+ };
+ }
+
+ this.bytesToStr = function(arr, offset, len)
+ {
+ var result = "";
+ var l = 0;
+ var i = offset;
+
+ while (l < len)
+ {
+ result += String.fromCharCode(arr[i]);
+ i++;
+ l++;
+ }
+
+ return result;
+ }
+
+ //Bytes are stored as little endians
+ this.bytesToNum = function(arr, offset, len)
+ {
+ var result = 0;
+ var l = 0;;
+ var i = offset + len - 1;
+ var hexstr = "0x";
+ var tmpstr;
+
+ while (l < len)
+ {
+ if (arr[i] >= 0 && arr[i] <= 15)
+ tmpstr = "0" + arr[i].toString(16);
+ else
+ tmpstr = arr[i].toString(16);
+
+ hexstr += tmpstr;
+ i--;
+ l++;
+ }
+
+ return parseInt(hexstr, 16);
+ }
+
+ this.createBuffers = function(nOut)
+ {
+ this.numberSamples = this.sampleRate*this.soundLength;
+
+ if (this.isChrome)
+ {
+ var b, d;
+
+ b = this.audioChrome.createBuffer(this.numberChannels, this.numberSamples, this.sampleRate);
+ d = b.getChannelData(0); //Float32Array
+ this.inSignal = {buffer: b, data: d, listen: true};
+
+ for (var i = 0; i < nOut; i++)
+ {
+
+ b = this.audioChrome.createBuffer(this.numberChannels, this.numberSamples, this.sampleRate);
+ d = b.getChannelData(0); //Float32Array
+ this.outSignals[i] = {buffer: b, data: d, listen: false};
+ }
+ }
+ else if (this.isMoz)
+ {
+ this.inSignal = {data: new Float32Array(this.numberSamples), listen: true};
+ for (var i = 0; i < nOut; i++)
+ {
+ this.outSignals[i] = {data: new Float32Array(this.numberSamples), listen: false};
+ }
+ this.audioMoz.mozSetup(this.numberChannels, this.sampleRate);
+ }
+ }
+
+ this.generateZero = function()
+ {
+ for (var i = 0, l = this.inSignal.data.length; i < l; i++)
+ {
+ this.inSignal.data[i] = 0;
+ }
+ }
+
+ this.generateUnitImpulse = function()
+ {
+ this.inSignal.data[0] = 1000;
+ for (var i = 1, l = this.inSignal.data.length; i < l; i++)
+ {
+ this.inSignal.data[i] = 0.0;
+ }
+ }
+
+ this.generateUnitStep = function()
+ {
+ for (var i = 0, l = this.inSignal.data.length; i < l; i++)
+ {
+ this.inSignal.data[i] = 1.0;
+ }
+ }
+
+ this.generateSineWave = function(peakToPeak, frequency, vOffset)
+ {
+ var amp = 0.5*peakToPeak;
+
+ if (vOffset != 0)
+ {
+ for (var i = 0, l = this.inSignal.data.length; i < l; i++)
+ {
+ this.inSignal.data[i] = amp * Math.sin(TWO_PI*frequency*i/this.sampleRate) + vOffset;
+ }
+ }
+ else
+ {
+ for (var i = 0, l = this.inSignal.data.length; i < l; i++)
+ {
+ this.inSignal.data[i] = amp * Math.sin(TWO_PI*frequency*i/this.sampleRate);
+ }
+ }
+ }
+
+ this.generateSquareWave = function(peakToPeak, frequency, vOffset)
+ {
+ var amp = 0.5*peakToPeak;
+ var period = 1/frequency;
+ var halfPeriod = period/2;
+ var itmp, sgn;
+
+
+ if (vOffset != 0)
+ {
+ for (var i = 0, l = this.inSignal.data.length; i < l; i++)
+ {
+ itmp = (i/this.sampleRate) % period;
+ if (itmp < halfPeriod)
+ sgn = sgn = 1;
+ else
+ sgn = -1;
+ this.inSignal.data[i] = amp * sgn + vOffset;
+ }
+ }
+ else
+ {
+ for (var i = 0, l = this.inSignal.data.length; i < l; i++)
+ {
+ itmp = (i/this.sampleRate) % period;
+ if (itmp < halfPeriod)
+ sgn = sgn = 1;
+ else
+ sgn = -1;
+ this.inSignal.data[i] = amp * sgn;
+ }
+ }
+ }
+
+ this.normalizeSound = function(arr)
+ {
+ var min = Number.POSITIVE_INFINITY;
+ var max = Number.NEGATIVE_INFINITY;
+ var vInMaxLocal = 10.0;
+ var maxVol = 1/vInMaxLocal;
+
+ //Find the min and max
+ for (var i = 0, l = arr.length; i < l; i++)
+ {
+ if (arr[i] > max)
+ max = arr[i];
+ if (arr[i] < min)
+ min = arr[i];
+ }
+
+ var vPeakToPeak = Math.abs(max - min);
+ var maxVol = vPeakToPeak / vInMaxLocal; //If we have a peak to peak voltage of 10 V, we want max sound, normalize to [-1, 1]
+ var norm = Math.max(Math.abs(min), Math.abs(max));
+
+ if (max != 0.0)
+ {
+ for (var i = 0, l = arr.length; i < l; i++)
+ {
+ arr[i] = maxVol*arr[i] / norm;
+ }
+ }
+ else //Fill in with zeros
+ {
+ for (var i = 0, l = arr.length; i < l; i++)
+ {
+ arr[i] = 0.0;
+ }
+ }
+ }
+
+ this.normalizeAllSounds = function()
+ {
+ //Normalize the sound buffer that will be heard
+ this.normalizeSound(this.inSignal.data);
+ for (var i = 0; i < this.outSignals.length; i++)
+ {
+ this.normalizeSound(this.outSignals[i].data);
+ }
+ }
+
+ this.playTone = function()
+ {
+ this.soundStarted();
+ var self = this;
+ if (this.isChrome)
+ {
+ this.outSrc = this.audioChrome.createBufferSource();
+
+ if (this.inSignal.listen)
+ this.outSrc.buffer = this.inSignal.buffer;
+ else
+ {
+ for (var i = 0; i < this.outSignals.length; i++)
+ {
+ if (this.outSignals[i].listen)
+ this.outSrc.buffer = this.outSignals[i].buffer;
+ }
+ }
+
+ this.outSrc.connect(this.audioChrome.destination);
+ this.outSrc.noteOn(0);
+ this.isPlaying = true;
+ this.chromeTimer = setTimeout(function(){
+ self.isPlaying = false;
+ self.soundStopped();
+ }, this.outSrc.buffer.duration * 1000);
+
+ }
+ else if (this.isMoz)
+ {
+ var playedAudioData;
+ var currentWritePosition = 0;
+ var currentPlayPosition = 0;
+ var prebufferSize = 22050 / 2; // buffer 500ms
+ var tail = null;
+
+ if (this.inSignal.listen)
+ playedAudioData = this.inSignal.data;
+ else
+ {
+ for (var i = 0; i < this.outSignals.length; i++)
+ {
+ if (this.outSignals[i].listen)
+ playedAudioData = this.outSignals[i].data;
+ }
+ }
+
+ this.isPlaying = true;
+
+ // The function called with regular interval to populate the audio output buffer.
+ this.playAudio = setInterval(function()
+ {
+ var written;
+ currentPlayPosition = self.audioMoz.mozCurrentSampleOffset();
+
+ // Check if some data was not written in previous attempts.
+ if (tail)
+ {
+ written = self.audioMoz.mozWriteAudio(tail);
+ currentWritePosition += written;
+ if (written < tail.length)
+ {
+ // Not all the data was written, saving the tail...
+ tail = tail.subarray(written);
+ return; //... and exit the function.
+ }
+ tail = null;
+ }
+
+ // Check if we need add some data to the audio output
+ var available = Math.floor(currentPlayPosition + prebufferSize - currentWritePosition);
+ if (available > 0)
+ {
+ var data = playedAudioData.subarray(currentWritePosition);
+ // Writting the data
+ written = self.audioMoz.mozWriteAudio(data);
+ // Not all the data was written, saving the tail
+ if(written <= data.length)
+ tail = data.subarray(written);
+ currentWritePosition += written;
+ }
+ }, 100);
+
+ this.mozTimer = setTimeout(function(){
+ clearInterval(self.playAudio);
+ self.isPlaying = false;
+ self.soundStopped();
+ }, this.soundLength*1000);
+ }
+ }
+
+ this.stopTone = function()
+ {
+ if (this.isPlaying)
+ {
+ if (this.isChrome)
+ {
+ clearTimeout(this.chromeTimer);
+ this.outSrc.noteOff(0);
+ }
+ else if (this.isMoz)
+ {
+ clearTimeout(this.mozTimer);
+ clearInterval(this.playAudio);
+ }
+ this.isPlaying = false;
+ }
+ this.soundStopped();
+ }
+ }
+
+ //////////PUBLIC FIELDS AND METHODS//////////
+ return {
+ Player: Player
+ };
+}());
diff --git a/common/test/data/full/subs/3NIegrCmA5k.srt.sjson b/common/test/data/full/subs/3NIegrCmA5k.srt.sjson
new file mode 100644
index 0000000000..bc505bb0d1
--- /dev/null
+++ b/common/test/data/full/subs/3NIegrCmA5k.srt.sjson
@@ -0,0 +1,5 @@
+{
+ "start": [],
+ "end": [],
+ "text": []
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/4cLA-IME32w.srt.sjson b/common/test/data/full/subs/4cLA-IME32w.srt.sjson
new file mode 100644
index 0000000000..bc505bb0d1
--- /dev/null
+++ b/common/test/data/full/subs/4cLA-IME32w.srt.sjson
@@ -0,0 +1,5 @@
+{
+ "start": [],
+ "end": [],
+ "text": []
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/8kARlsUt9lM.srt.sjson b/common/test/data/full/subs/8kARlsUt9lM.srt.sjson
new file mode 100644
index 0000000000..bc505bb0d1
--- /dev/null
+++ b/common/test/data/full/subs/8kARlsUt9lM.srt.sjson
@@ -0,0 +1,5 @@
+{
+ "start": [],
+ "end": [],
+ "text": []
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/8rK9vnpystQ.srt.sjson b/common/test/data/full/subs/8rK9vnpystQ.srt.sjson
new file mode 100644
index 0000000000..bc505bb0d1
--- /dev/null
+++ b/common/test/data/full/subs/8rK9vnpystQ.srt.sjson
@@ -0,0 +1,5 @@
+{
+ "start": [],
+ "end": [],
+ "text": []
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/BGU1poJDgOY.srt.sjson b/common/test/data/full/subs/BGU1poJDgOY.srt.sjson
new file mode 100644
index 0000000000..1039ea92c6
--- /dev/null
+++ b/common/test/data/full/subs/BGU1poJDgOY.srt.sjson
@@ -0,0 +1,275 @@
+{
+ "start": [
+ 0,
+ 410,
+ 2390,
+ 5510,
+ 7110,
+ 10400,
+ 13120,
+ 15150,
+ 18850,
+ 20800,
+ 25570,
+ 27950,
+ 32870,
+ 36560,
+ 38920,
+ 40850,
+ 43130,
+ 46690,
+ 48640,
+ 50910,
+ 54260,
+ 56872,
+ 59590,
+ 63370,
+ 66290,
+ 68000,
+ 71320,
+ 74130,
+ 77880,
+ 80860,
+ 81900,
+ 85380,
+ 89890,
+ 93550,
+ 97980,
+ 100960,
+ 104010,
+ 105450,
+ 107320,
+ 110600,
+ 114310,
+ 116940,
+ 118800,
+ 122550,
+ 124950,
+ 127000,
+ 129479,
+ 131740,
+ 133570,
+ 137170,
+ 141410,
+ 143740,
+ 146820,
+ 148870,
+ 152620,
+ 157640,
+ 162970,
+ 164060,
+ 167630,
+ 168680,
+ 171890,
+ 173700,
+ 177560,
+ 181850,
+ 185390,
+ 189290,
+ 191550,
+ 196470,
+ 197780,
+ 202150,
+ 204550,
+ 208200,
+ 210910,
+ 213870,
+ 218830,
+ 222090,
+ 225070,
+ 229290,
+ 233580,
+ 236700,
+ 239510,
+ 242110,
+ 243770,
+ 246990,
+ 247950,
+ 251740,
+ 255340,
+ 256500,
+ 258759
+ ],
+ "end": [
+ 410,
+ 2390,
+ 5510,
+ 7109,
+ 10400,
+ 13120,
+ 15149,
+ 18850,
+ 20800,
+ 25570,
+ 27950,
+ 32870,
+ 36559,
+ 38920,
+ 40850,
+ 43130,
+ 46690,
+ 48640,
+ 50910,
+ 54260,
+ 56872,
+ 59590,
+ 63370,
+ 66289,
+ 68000,
+ 71320,
+ 74130,
+ 77880,
+ 80860,
+ 81900,
+ 85380,
+ 89890,
+ 93550,
+ 97979,
+ 100960,
+ 104009,
+ 105450,
+ 107320,
+ 110600,
+ 114309,
+ 116940,
+ 118800,
+ 122550,
+ 124950,
+ 127000,
+ 129479,
+ 131740,
+ 133570,
+ 137170,
+ 141410,
+ 143740,
+ 146820,
+ 148870,
+ 152620,
+ 157640,
+ 162970,
+ 164060,
+ 167630,
+ 168680,
+ 171890,
+ 173700,
+ 177560,
+ 181850,
+ 185390,
+ 189290,
+ 191549,
+ 196470,
+ 197780,
+ 202150,
+ 204550,
+ 208200,
+ 210910,
+ 213870,
+ 218830,
+ 222090,
+ 225070,
+ 229290,
+ 233579,
+ 236700,
+ 239510,
+ 242109,
+ 243770,
+ 246990,
+ 247950,
+ 251739,
+ 255340,
+ 256500,
+ 258759,
+ 260009
+ ],
+ "text": [
+ null,
+ "Let us summarize what we've\nlearned so far.",
+ "So we started out with building\nthis playground, this",
+ "EECS playground.",
+ "And within this playground,\nwe've all decided that we will",
+ "abide by the lumped matter\ndiscipline, the LMD.",
+ "And these are simply constraints\nthat we impose",
+ "upon ourselves to simplify\nour analysis.",
+ "Notice that, you really\ndon't have to do that.",
+ "You are welcome to hang out here\nand do whatever you want.",
+ "But you might have a\nlot of fun as well.",
+ "But, you will not be able to\nbuild things as simply as you",
+ "would if you were inside\nthe EECS playground.",
+ "But, Within this playground\nyou have promised that you",
+ "will follow the lumped\nmatter discipline.",
+ "Lumped matter discipline has\nthree components to it.",
+ "One is that the elements will\nhave del phi b by del t equals",
+ "zero, outside the elements.",
+ "In other words, when you build\ncircuits, you want to make",
+ "sure that the del phi b by del\nt is going to be zero.",
+ "And inside the elements, we are\ngoing to make sure that",
+ "when you define something--\ndescribe something as a lumped",
+ "element, del q by del t,\nfor that element--",
+ "the net del q by del t for\nthat element inside the",
+ "element is going to be zero.",
+ "And that's going to be true\nfor all your elements,",
+ "registers, sources,\nwires, and so on.",
+ "And the third element is that\nyou will only focus on signal",
+ "speeds of interest that\nwill be way lower than",
+ "the speed of light.",
+ "Now one of the interesting facts\nof life is that as our",
+ "microprocessors get faster and\nfaster, some of the chips",
+ "inside your iPhones, and your\ndesktops, and so on, are",
+ "clocking at speeds in excess\nof three to four gigahertz.",
+ "When signal speeds are that\nfast, we are now getting to a",
+ "point where the signal speeds\ninside our chips are",
+ "approaching the speed\nof light.",
+ "So they're not all\nthat far off.",
+ "And so therefore, over the next\ndecade, as people try to",
+ "push speeds to even higher\nvalues, various things are",
+ "going to start breaking unless\nwe change things",
+ "dramatically somehow.",
+ "What this does is these\ndiscipline elements, the",
+ "lumped matter discipline,\nenables us to create the",
+ "lumped circuit abstraction.",
+ "Look at our lumped elements.",
+ "Lumped elements will\nbe described",
+ "by their v-i relation.",
+ "So each of these lumped elements\nof that sum i, that",
+ "would be sum function of\nv, the v-i relation.",
+ "The relation could be linear,\ncould be non-linear.",
+ "Could be, in general, could be\nanything, just that they have",
+ "some v-i relation.",
+ "For the register you saw that\ni was v divided by R.",
+ "For some other element, it could\nbe that i is 6.7 times v",
+ "squared plus v divided by\n2 plus some constant 1.",
+ "I mean, who knows.",
+ "That could be some function of\nv. Could be linear, could be",
+ "non-linear.",
+ "And for this element, the power\nconsumed by the element",
+ "is given by vi.",
+ "Finally, we showed that when\nyou make the lumped matter",
+ "discipline assumptions,\nMaxwell's equations simplify",
+ "to extremely simple\nKVL and KCL.",
+ "So, for example, KVL simply\nsays that the sum of the",
+ "voltages around a\nloop is zero.",
+ "While KCL says that the sum of\nthe current that go into a",
+ "node is zero.",
+ "You can also say the currents\ncoming out of a node are zero",
+ "by the same principle.",
+ "What is amazing about this is\nthat by making the lumped",
+ "matter discipline, by abiding\nby the lumped matter",
+ "discipline, we have been able\nto convert the use of",
+ "Maxwell's equation into the use\nof very simple algebra.",
+ "So we replaced our del phi's and\ndel t's, and all of that",
+ "stuff, with extremely\nsimple algebra.",
+ "And in the next set of segments\nI'm going to show you",
+ "how we can simplify and analyze\ncircuits extremely",
+ "easily using very, very\nsimple algebra.",
+ "And, in fact, for the first half\nof this course, you are",
+ "not going to see any\nsquigglies at all.",
+ "It's going to be very,\nvery simple.",
+ "And even later, we're not going\nto be seeing any partial",
+ "differential equations.",
+ "We will see some fairly simple\ndifferential equations, but by",
+ "making the LMD assumptions, we\nhave really gotten rid of a",
+ "lot of the complexity.",
+ "And things are going to be\nreally, really, really simple.",
+ null
+ ]
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/CcgAYu0n0bg.srt.sjson b/common/test/data/full/subs/CcgAYu0n0bg.srt.sjson
new file mode 100644
index 0000000000..bc505bb0d1
--- /dev/null
+++ b/common/test/data/full/subs/CcgAYu0n0bg.srt.sjson
@@ -0,0 +1,5 @@
+{
+ "start": [],
+ "end": [],
+ "text": []
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/EG-fRTJln_E.srt.sjson b/common/test/data/full/subs/EG-fRTJln_E.srt.sjson
new file mode 100644
index 0000000000..bc505bb0d1
--- /dev/null
+++ b/common/test/data/full/subs/EG-fRTJln_E.srt.sjson
@@ -0,0 +1,5 @@
+{
+ "start": [],
+ "end": [],
+ "text": []
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/G_2F9wivspM.srt.sjson b/common/test/data/full/subs/G_2F9wivspM.srt.sjson
new file mode 100644
index 0000000000..064fc99ed5
--- /dev/null
+++ b/common/test/data/full/subs/G_2F9wivspM.srt.sjson
@@ -0,0 +1,212 @@
+{
+ "start": [
+ 0,
+ 1800,
+ 2440,
+ 7270,
+ 11690,
+ 13030,
+ 17060,
+ 21240,
+ 24980,
+ 29930,
+ 33740,
+ 37930,
+ 40340,
+ 46830,
+ 50200,
+ 52890,
+ 56355,
+ 59450,
+ 60430,
+ 66640,
+ 69640,
+ 72148,
+ 73398,
+ 77730,
+ 83080,
+ 84330,
+ 99630,
+ 103660,
+ 110800,
+ 114250,
+ 118220,
+ 121940,
+ 131580,
+ 134300,
+ 137730,
+ 140540,
+ 143860,
+ 146050,
+ 147300,
+ 149850,
+ 157140,
+ 161820,
+ 164460,
+ 165600,
+ 169520,
+ 174040,
+ 176290,
+ 178310,
+ 181070,
+ 182920,
+ 184160,
+ 189960,
+ 192630,
+ 193480,
+ 199020,
+ 201540,
+ 204520,
+ 208180,
+ 211470,
+ 217120,
+ 220790,
+ 224490,
+ 227200,
+ 229150,
+ 232940,
+ 236320,
+ 239070,
+ 242480
+ ],
+ "end": [
+ 1800,
+ 2440,
+ 7270,
+ 11690,
+ 13030,
+ 17060,
+ 21240,
+ 24979,
+ 29930,
+ 33740,
+ 37930,
+ 40340,
+ 46830,
+ 50199,
+ 52890,
+ 56355,
+ 59449,
+ 60430,
+ 66640,
+ 69640,
+ 72148,
+ 73398,
+ 77729,
+ 83080,
+ 84330,
+ 99630,
+ 103660,
+ 110800,
+ 114250,
+ 118220,
+ 121940,
+ 131579,
+ 134300,
+ 137730,
+ 140540,
+ 143859,
+ 146050,
+ 147300,
+ 149850,
+ 157140,
+ 161820,
+ 164459,
+ 165600,
+ 169519,
+ 174040,
+ 176290,
+ 178310,
+ 181070,
+ 182920,
+ 184160,
+ 189960,
+ 192630,
+ 193480,
+ 199019,
+ 201540,
+ 204519,
+ 208180,
+ 211470,
+ 217120,
+ 220790,
+ 224489,
+ 227200,
+ 229149,
+ 232940,
+ 236320,
+ 239070,
+ 242480,
+ 243730
+ ],
+ "text": [
+ "SPEAKER 1: So I want to show\nyou a simple circuit that",
+ "looks like this.",
+ "And let's go ahead and measure\nsome voltages and currents.",
+ "In terms of terminology,\nremember",
+ "this is called a loop.",
+ "So if I start from the point\nC, and I travel through the",
+ "voltage source, come to the node\nA, down through R1 and",
+ "all the way down through R2\nback to C, that's a loop.",
+ "Similarly, this point A is a\nnode where the register R1,",
+ "the voltage source V0 and\nR4 are connected.",
+ "Just make sure your terminology\nis correct.",
+ "So what I'll do is let me make\nsome quick measurements for",
+ "you, and show you that these KVL\nand KCL are indeed true.",
+ "So the circuit's up there.",
+ "So let me take some\nmeasurements.",
+ "And why don't you write down\nwhat I measure on the board.",
+ null,
+ "Let me borrow another\npiece of chalk here.",
+ "What I'll do is focus on this\nloop here and focus on this",
+ "node and make some\nmeasurements.",
+ "So you see the circuit\nup there.",
+ "[INAUDIBLE]",
+ null,
+ "So I get three volts for the\nvoltage from C to A. So why",
+ "don't you write down\nthree volts.",
+ null,
+ "OK, so the next one\nis minus 1.6.",
+ null,
+ "And so that will be\nI'm doing AB, VAB.",
+ null,
+ "And then let me do\nthe last one.",
+ "And it is minus 1.37.",
+ "Within the bonds of experimental\nerror, notice",
+ "that if I add up these\nthree voltages, they",
+ "nicely sum up to zero.",
+ "OK, next let me focus\non this node here.",
+ "And at this node, let\nme go ahead and",
+ "measure some currents.",
+ null,
+ "What I'll do now is change to an\nAC voltage so that I can go",
+ "ahead and measure the current\nwithout breaking my circuit.",
+ "This time around, you'll get to\nsee the measurements that",
+ "I'm taking as well.",
+ "What I have here is three wires\nthat I have pulled out",
+ "from D. And this is the node D.\nSo three wires coming into",
+ "the node D just to make it a\nlittle bit easier for me to",
+ "measure stuff.",
+ "So everybody keep your fingers\ncrossed so I don't",
+ "look like a fool here.",
+ "Hope this works out.",
+ "So it's about 10 millivolts\npeak to peak out there.",
+ "Let's say that if the wave form\nrises on the left hand",
+ "side, it's positive.",
+ "So it's positive\n10 millivolts.",
+ "And another positive\n10 millivolts.",
+ "So that's 20 millivolts.",
+ "And this time it's a negative,\nroughly 20, I",
+ "guess, negative 20.",
+ "So I'm getting, in terms of\ncurrents, I have a positive",
+ "10, a positive 10 and a negative\n20 that adds up to 0.",
+ "But more interestingly, I can\nshow you the same thing by",
+ "holding this current\nmeasuring probe",
+ "directly across that node.",
+ "And notice that the net current\nthat is entering into",
+ "this node here is zero.",
+ "So that should just show you\nthat KCL does indeed hold in",
+ "practice, and it is not just a\nfigment of our imaginations.",
+ null
+ ]
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/XbDRmF6J0K0.srt.sjson b/common/test/data/full/subs/XbDRmF6J0K0.srt.sjson
new file mode 100644
index 0000000000..7f75406826
--- /dev/null
+++ b/common/test/data/full/subs/XbDRmF6J0K0.srt.sjson
@@ -0,0 +1,266 @@
+{
+ "start": [
+ 0,
+ 3780,
+ 5290,
+ 9590,
+ 13370,
+ 13735,
+ 15275,
+ 16740,
+ 19790,
+ 23420,
+ 26470,
+ 28560,
+ 31630,
+ 33770,
+ 35320,
+ 36750,
+ 38390,
+ 41660,
+ 43720,
+ 46740,
+ 47805,
+ 49920,
+ 52170,
+ 53540,
+ 56530,
+ 59350,
+ 63350,
+ 66470,
+ 68440,
+ 69220,
+ 71320,
+ 72870,
+ 78300,
+ 79890,
+ 82470,
+ 85670,
+ 90720,
+ 93660,
+ 97005,
+ 98280,
+ 102090,
+ 103460,
+ 107090,
+ 108040,
+ 110700,
+ 112510,
+ 117400,
+ 121890,
+ 124450,
+ 130229,
+ 133180,
+ 140010,
+ 142500,
+ 143580,
+ 150010,
+ 152720,
+ 159180,
+ 161330,
+ 165510,
+ 171270,
+ 173750,
+ 177000,
+ 179340,
+ 181273,
+ 182400,
+ 188140,
+ 189210,
+ 192730,
+ 199620,
+ 206460,
+ 208220,
+ 211810,
+ 214510,
+ 219790,
+ 221440,
+ 223910,
+ 224950,
+ 226060,
+ 231860,
+ 232940,
+ 237990,
+ 240450,
+ 242470,
+ 244760,
+ 246470,
+ 248420
+ ],
+ "end": [
+ 3780,
+ 5290,
+ 9590,
+ 13370,
+ 13735,
+ 15275,
+ 16740,
+ 19790,
+ 23420,
+ 26470,
+ 28560,
+ 31630,
+ 33770,
+ 35320,
+ 36750,
+ 38390,
+ 41660,
+ 43720,
+ 46740,
+ 47805,
+ 49920,
+ 52170,
+ 53540,
+ 56530,
+ 59350,
+ 63350,
+ 66470,
+ 68440,
+ 69220,
+ 71320,
+ 72870,
+ 78300,
+ 79890,
+ 82470,
+ 85670,
+ 90720,
+ 93660,
+ 97005,
+ 98280,
+ 102090,
+ 103460,
+ 107090,
+ 108040,
+ 110700,
+ 112510,
+ 117400,
+ 121890,
+ 124450,
+ 130229,
+ 133180,
+ 140010,
+ 142500,
+ 143580,
+ 150010,
+ 152720,
+ 159180,
+ 161330,
+ 165510,
+ 171270,
+ 173750,
+ 177000,
+ 179340,
+ 181273,
+ 182400,
+ 188140,
+ 189210,
+ 192730,
+ 196700,
+ 206460,
+ 208220,
+ 211810,
+ 214510,
+ 219790,
+ 221440,
+ 223910,
+ 224950,
+ 226060,
+ 231860,
+ 232940,
+ 237990,
+ 240450,
+ 242470,
+ 244760,
+ 246470,
+ 248420,
+ 249670
+ ],
+ "text": [
+ "SPEAKER 1: We just learned the fundamental rules of",
+ "electrical circuits.",
+ "For example, if we have a circuit like this, which is",
+ "called a bridge and which has a voltage source and five",
+ "resistors--",
+ "SPEAKER 2: And I'll show the circuit in lecture.",
+ "SPEAKER 1: That's right.",
+ "We had a bunch of voltage current constraints, one for",
+ "each of the branches, each of the elements in the network.",
+ "For example, this one, the voltage across it is the",
+ "voltage intrinsic to it.",
+ "And we have Kirchoff's Current Laws, one for each of the",
+ "possible nodes in the network.",
+ "So that's four of them here.",
+ "It turns out one of them is redundant.",
+ "It can be derived from the others.",
+ "And we have Kirchoff Voltage Laws, which are some of the",
+ "voltages around the loop is zero.",
+ "And I have four of them written down here, and only",
+ "three of them are independent.",
+ "But we can have many more because there's a loop I",
+ "didn't show, which is the one that looks like this, for",
+ "example, OK?",
+ "And the other thing we have is that some of the powers on the",
+ "network entering each the elements is zero.",
+ "So there's no power gained or lost. This is a general method",
+ "of solving all circuit problems. Unfortunately, even",
+ "for a simple circuit like this, we have a mess of",
+ "equations here.",
+ "This is not a good idea.",
+ "SPEAKER 2: So in the next lecture, what Anant will show",
+ "you is a much more coherent, a much better organized",
+ "technique for solving these circuits.",
+ "So again, if we circle this circuit--",
+ "this is the same brood circuit Anant showed in the lecture--",
+ "we have six resistors, one, two, three, four nodes.",
+ "And what we do is we take each of these nodes and for those",
+ "nodes we'll write Kirchoff's Current Law.",
+ "And that's called nodal analysis.",
+ "So step zero is we pick one of these nodes and we label that",
+ "node as ground.",
+ "And labeling it ground just says the voltage on",
+ "that node is zero.",
+ "SPEAKER 1: The potential on that node is zero.",
+ "Voltages are differences of potential.",
+ "SPEAKER 2: Then we take a node, such as b.",
+ "And for that node, b, we write down an equation that says the",
+ "currents going into that node are o0.",
+ "So IR1 is plus IR2 plus IR3 is equal to zero.",
+ "We take this node, d, where we also write down Kirchoff's",
+ "Current Law, IR3 plus IR4 plus IR5 is equal to zero.",
+ "And at that point, we substitute",
+ "in for those voltages.",
+ "So when we have IR3, that's just b minus d over R3.",
+ "That's the current going into it.",
+ "For R5 over here, we have just d--",
+ "IS is 0 minus d.",
+ "So that's just minus d over R5.",
+ "And for IR4, we have a minus d over R4.",
+ "So we have an equation like this.",
+ "SPEAKER 1: So these labels, a, b and d, are within node",
+ "potentials, OK?",
+ "SPEAKER 2: We do the same thing over here for--",
+ "SPEAKER 1: Right, yes.",
+ "SPEAKER 2: We do the same thing over here for IR2 is--",
+ "SPEAKER 1: It's IR1.",
+ "SPEAKER 2: IR1 is a minus b over R1.",
+ "IR3 is d minus b over R3.",
+ "And finally, IR2 is minus b over R2.",
+ "So now we have two equations.",
+ "And the last thing that we do is we label this node a to",
+ "have the voltage v. We say a is equal to v.",
+ "Since this is a zero, instead of a, it's v, we make that",
+ "substitution everywhere here.",
+ "We have two equations, two unknowns.",
+ "We solve it.",
+ "And that gives us our answer.",
+ "SPEAKER 1: So the equations are b and d because all the",
+ "A's are substituted for.",
+ "And all of the KCLs that were written with the KCLs for some",
+ "of the currents entering the node is zero.",
+ "You could have equally written them as some of the currents",
+ "leaving the node as zero and get the same answer because",
+ "that would just multiply everything by minus 1.",
+ "SPEAKER 2: So you'll see this next week.",
+ "That's just a preview of coming attractions."
+ ]
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/eLAyO33baQ8.srt.sjson b/common/test/data/full/subs/eLAyO33baQ8.srt.sjson
new file mode 100644
index 0000000000..a6cd4edabd
--- /dev/null
+++ b/common/test/data/full/subs/eLAyO33baQ8.srt.sjson
@@ -0,0 +1,335 @@
+{
+ "start": [
+ 0,
+ 430,
+ 3070,
+ 5680,
+ 9120,
+ 12590,
+ 13280,
+ 17280,
+ 20200,
+ 23630,
+ 26720,
+ 29450,
+ 32700,
+ 35920,
+ 39280,
+ 43310,
+ 44850,
+ 46400,
+ 48270,
+ 52110,
+ 54140,
+ 57570,
+ 61600,
+ 63040,
+ 64410,
+ 66980,
+ 69920,
+ 72490,
+ 75110,
+ 77540,
+ 80680,
+ 84430,
+ 87190,
+ 91790,
+ 94420,
+ 97830,
+ 101030,
+ 104180,
+ 106960,
+ 108320,
+ 110885,
+ 113180,
+ 115630,
+ 119930,
+ 122740,
+ 129590,
+ 131000,
+ 134200,
+ 138470,
+ 142200,
+ 145410,
+ 150500,
+ 153590,
+ 158000,
+ 160290,
+ 164600,
+ 167370,
+ 169272,
+ 173420,
+ 176650,
+ 180460,
+ 183390,
+ 185200,
+ 188940,
+ 190750,
+ 194040,
+ 196510,
+ 198060,
+ 202670,
+ 206860,
+ 209980,
+ 213620,
+ 219130,
+ 221760,
+ 222700,
+ 227050,
+ 230340,
+ 234810,
+ 236930,
+ 239030,
+ 241390,
+ 246630,
+ 248200,
+ 252220,
+ 255370,
+ 257160,
+ 258709,
+ 263370,
+ 264280,
+ 266840,
+ 270190,
+ 275640,
+ 277790,
+ 280880,
+ 282680,
+ 283930,
+ 285990,
+ 287870,
+ 290740,
+ 294140,
+ 299760,
+ 305270,
+ 308230,
+ 310160,
+ 311540,
+ 314150,
+ 316160,
+ 316440,
+ 318500
+ ],
+ "end": [
+ 430,
+ 3070,
+ 5680,
+ 9120,
+ 12590,
+ 13280,
+ 17280,
+ 20200,
+ 23630,
+ 26720,
+ 29450,
+ 32700,
+ 35920,
+ 39280,
+ 43310,
+ 44850,
+ 46400,
+ 48269,
+ 52110,
+ 54140,
+ 57570,
+ 61600,
+ 63040,
+ 64410,
+ 66979,
+ 69920,
+ 72490,
+ 75110,
+ 77540,
+ 80680,
+ 84430,
+ 87190,
+ 91789,
+ 94420,
+ 97830,
+ 101030,
+ 104180,
+ 106960,
+ 108320,
+ 110884,
+ 113180,
+ 115630,
+ 119929,
+ 122740,
+ 129590,
+ 131000,
+ 134200,
+ 138470,
+ 142200,
+ 145410,
+ 150500,
+ 153590,
+ 158000,
+ 160290,
+ 164600,
+ 167370,
+ 169272,
+ 173420,
+ 176649,
+ 180460,
+ 183390,
+ 185200,
+ 188940,
+ 190750,
+ 194040,
+ 196510,
+ 198060,
+ 202670,
+ 206859,
+ 209980,
+ 213619,
+ 219130,
+ 221760,
+ 222700,
+ 227049,
+ 230340,
+ 234810,
+ 236930,
+ 239030,
+ 241390,
+ 246630,
+ 248200,
+ 252220,
+ 255370,
+ 257160,
+ 258709,
+ 263370,
+ 264280,
+ 266840,
+ 270190,
+ 275640,
+ 277789,
+ 280880,
+ 282680,
+ 283930,
+ 285990,
+ 287870,
+ 290740,
+ 294140,
+ 299760,
+ 305270,
+ 308229,
+ 310160,
+ 311540,
+ 314150,
+ 316159,
+ 316440,
+ 318500,
+ 319750
+ ],
+ "text": [
+ null,
+ "SPEAKER 1: In this sequence, are\ngoing to look at a whole",
+ "bunch of techniques for\nanalyzing circuits.",
+ "Think of these as your tool\nchest. So you are embarking on",
+ "6.002 and going on to build\nlots of useful electronic",
+ "systems.",
+ "But before we go and build these\nsystems, much like a",
+ "carpenter who needs to go build\na house, much like a",
+ "carpenter has to go and buy a\nset of tools from the local",
+ "store, we need to go and make\nsure that you have the tools",
+ "in your tool chest to analyze\nthese circuits.",
+ "This tool chest will include\nthings like the KVL KCL",
+ "method, the Node method\nand so on.",
+ "But before I begin talking about\nthese techniques for",
+ "your tool chest, I'd like to do\na extremely quick review--",
+ "as is my usual practice--",
+ "of what you've covered so far.",
+ "So remember our EECS\nplayground.",
+ "We've gotten into this place by\npromising that we are going",
+ "to observe the lumped\nmatter discipline.",
+ "By observing the discipline\nrecall that the complexities",
+ "of Maxwell's equations and the\ndifferential equations that",
+ "you have to solve-- the\npartial differential",
+ "equations-- goes away.",
+ "And you're left with very\nsimple linear equations.",
+ "And you will see a lot\nof that today.",
+ "As you make the lumped matter\ndiscipline, you end up with",
+ "these lumped elements, like\nresistors and voltage",
+ "sources and so on.",
+ "And what you do with these\nlumped elements is we can",
+ "label what are called the\nbranch variables or the",
+ "terminal variables for these\nlumped elements, OK?",
+ "So for example, v is the voltage\nacross the element.",
+ "And i is the current through\nthe element.",
+ "The power consumed by the\nelement is given by vi.",
+ "Continuing with our review, the\nlumped matter discipline",
+ "enables us to create the lumped\ncircuit abstraction.",
+ "So you take these lumped\nelements and you connect them",
+ "with ideal wires.",
+ "So in this case, I have a\nresistor which is a lumped",
+ "element, a voltage source.",
+ "And I connect them with\nthese ideal wires.",
+ "And what you end up with is\ncalled a lumped circuit.",
+ "But this is your lumped\ncircuit abstraction.",
+ "Well as we made the transition\nfrom physics to EECS by",
+ "adhering to the lumped\nmatter discipline.",
+ "I had discussed in the last\nsequence that Maxwell's",
+ "equations turn into very simple\nalgebraic equations.",
+ "And these are captured by\nKirchoff's voltage and current",
+ "laws, called KVL and KCL.",
+ "So KVL says that for all loops\nin your circuit, the sum of",
+ "the voltages around the\nloops add up to zero.",
+ "Similarly for all the nodes in\nthe circuit, the currents that",
+ "enter into a node\nadd up to zero.",
+ "And in the same matter, if I\njust summed the currents",
+ "leaving a node, they also\nadd up to zero.",
+ "So whether you sum the currents\nentering the node or",
+ "currents leaving a node, they\nwould add up to zero by KCL.",
+ "And the beauty of this was by\nadhering to lump matter",
+ "discipline, recall the\ncomplexities Maxwell's",
+ "equations whether an\nintegral form or",
+ "differential form went away.",
+ "And we were now left with\nextremely simple algebraic",
+ "expressions of the sort.",
+ "And you will see shortly that\nyou will end up solving",
+ "extremely simple linear\nequations",
+ "as we analyze circuits.",
+ "So as one example here\nis a linear circuit.",
+ "It has one, two, three, four,\nfive, six elements.",
+ "It's got a voltage source\nof a voltage v zero.",
+ "It's got a resistor, r one\nand so on and so forth.",
+ "So for this circuit according\nto our abstraction and the",
+ "laws of KVL and KCL,\nthe following",
+ "are going to be true.",
+ "So for instance if I look at\nnode a, then according to KCL,",
+ "the currents entering the\nnode must be zero.",
+ "So in other words, the\ncurrent here is ica.",
+ "And I can sum to that\nthe current",
+ "entering from the d direction.",
+ "So that would be plus ida.",
+ "And then there is a current from\nthe ba direction and iba.",
+ "Those are going to\nsum to zero.",
+ "Now I can multiply the whole\nthing by minus 1.",
+ "And that would give me the sum\nof the currents leaving the",
+ "node are also going\nto be zero.",
+ "So that is KCL.",
+ "Now I can also write KVL in this\ncase for this loop, for",
+ "instance, here.",
+ "For this loop I can add up the\nvoltages around the loop.",
+ "And by KVL they must\nadd up to zero.",
+ "So for example if I look at\nthe voltage vca, I can add",
+ "that to the next voltage here.",
+ "That is to vab and then\nadd that to the",
+ "final voltage, vbc.",
+ "And by KCL--",
+ null,
+ "so all of those must\nadd up to be zero.",
+ "Now if you go back to our\ncircuit here that you see on",
+ "the left hand side, I am going\nto show you a little demo that",
+ "will look at these currents,\nica, ida, and iba, and also",
+ "look at the voltages, vcabab\nand bbc and actually go and",
+ "look at the circuit, make the\nmeasurements, and show that",
+ "they indeed sum up to zero.",
+ "Not surprising.",
+ "KVL and KCL really work.",
+ "And so the demo should\nalso work.",
+ "OK.",
+ "So let's go and do our demo.",
+ null
+ ]
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/p2Q6BrNhdh8.srt.sjson b/common/test/data/full/subs/p2Q6BrNhdh8.srt.sjson
new file mode 100644
index 0000000000..5168cbab01
--- /dev/null
+++ b/common/test/data/full/subs/p2Q6BrNhdh8.srt.sjson
@@ -0,0 +1,155 @@
+{
+ "start": [
+ 0,
+ 6830,
+ 9040,
+ 11810,
+ 15030,
+ 19420,
+ 21690,
+ 24880,
+ 29850,
+ 32409,
+ 35430,
+ 37550,
+ 40240,
+ 44050,
+ 47470,
+ 50910,
+ 52750,
+ 55340,
+ 57870,
+ 59920,
+ 62450,
+ 66320,
+ 70470,
+ 72100,
+ 82700,
+ 84820,
+ 86550,
+ 88890,
+ 93560,
+ 94810,
+ 98620,
+ 100040,
+ 102740,
+ 105880,
+ 108470,
+ 111550,
+ 112830,
+ 116190,
+ 117410,
+ 120620,
+ 122310,
+ 125810,
+ 129720,
+ 132900,
+ 135170,
+ 138020,
+ 141350,
+ 143230,
+ 144480
+ ],
+ "end": [
+ 6830,
+ 9040,
+ 11809,
+ 15030,
+ 19419,
+ 21690,
+ 24880,
+ 29849,
+ 32409,
+ 35430,
+ 37550,
+ 40239,
+ 44050,
+ 47470,
+ 50910,
+ 52750,
+ 55340,
+ 57870,
+ 59919,
+ 62450,
+ 66320,
+ 70470,
+ 72100,
+ 82699,
+ 84820,
+ 86550,
+ 88890,
+ 93560,
+ 94810,
+ 98620,
+ 100040,
+ 102740,
+ 105880,
+ 108470,
+ 111550,
+ 112830,
+ 116190,
+ 117410,
+ 120619,
+ 122310,
+ 125810,
+ 129720,
+ 132900,
+ 135170,
+ 138019,
+ 141350,
+ 143230,
+ 144480,
+ 152786
+ ],
+ "text": [
+ null,
+ "PROFESSOR ANANT AGARWAL:\nWelcome to MITx, MIT's",
+ "worldwide online learning\ninitiative.",
+ "We are delighted to have you\njoin us for 6.002x: Circuits",
+ "and Electronics, the inaugural\ncourse of MITx.",
+ "OK, welcome to 6.002.",
+ "6.002x is the same as Electronic\nCircuits course",
+ "taken by MIT students as the\nfirst course in an EE or EECS",
+ "major at MIT.",
+ "This course will help you make\nthe transition from physics to",
+ "electrical engineering.",
+ "In this course, you will learn\nabout a lot of fun things,",
+ "including the foundations behind\nmany cool devices, such",
+ "as the smartphone.",
+ "The course will be hard and we\nwant you to stick with us.",
+ "It is our hope that you\nwill have a lot",
+ "of fun in this course.",
+ "Let me start by introducing\nyou to our team.",
+ "I am Anant Agarwal.",
+ "I am the director of MIT's\nComputer Science and",
+ "Artificial Intelligence\nLaboratory or CSAIL.",
+ "Like most MIT faculty, I have\na wide range of interests,",
+ "including a fascination\naround chainsaws.",
+ null,
+ "GERRY SUSSMAN: Hello, I'm Gerry\nSussman, professor of",
+ "electrical engineering at MIT.",
+ "Been a faculty member\nhere since 1973.",
+ "I build electronics, optics,\ncomputer software, and I",
+ "repair watches as a hobby.",
+ null,
+ "PIOTR MITROS: I'm\nPiotr Mitros.",
+ "I'm a research scientist at MIT,\nand I was in charge of",
+ "developing the software\ninfrastructure for MITx.",
+ "As a graduate student, I spent a\nlot of time traveling around",
+ "India, Nigeria, and China,\nmostly as part of educational",
+ "technology projects.",
+ "So I'm very excited to be\nhelping out with this course.",
+ "PROFESSOR ANANT AGARWAL: Whether\nwe like chainsaws or",
+ "watches, we are all passionate\neducators.",
+ "This course is free.",
+ "You will have the opportunity\nto earn an MITx pilot",
+ "certificate with your grade\nat the end of this course.",
+ "In this experimental first\ncourse of MITx, the",
+ "certificates are also free.",
+ "Now, this is a prototype course\nand we welcome your",
+ "participation and any feedback\nyou might have to improving",
+ "the learning experience.",
+ "Let's get started.",
+ null
+ ]
+}
\ No newline at end of file
diff --git a/common/test/data/full/subs/pFOrD8k9_p4.srt.sjson b/common/test/data/full/subs/pFOrD8k9_p4.srt.sjson
new file mode 100644
index 0000000000..fabe43c22d
--- /dev/null
+++ b/common/test/data/full/subs/pFOrD8k9_p4.srt.sjson
@@ -0,0 +1,101 @@
+{
+ "start": [
+ 0,
+ 360,
+ 2460,
+ 6080,
+ 11270,
+ 12700,
+ 16210,
+ 18460,
+ 22750,
+ 26140,
+ 30360,
+ 31480,
+ 35370,
+ 37170,
+ 40710,
+ 43190,
+ 46980,
+ 49050,
+ 52590,
+ 55170,
+ 57100,
+ 59330,
+ 60950,
+ 65450,
+ 69710,
+ 73480,
+ 75100,
+ 77600,
+ 78790,
+ 81330,
+ 82580
+ ],
+ "end": [
+ 360,
+ 2460,
+ 6080,
+ 11270,
+ 12700,
+ 16210,
+ 18460,
+ 22750,
+ 26140,
+ 30360,
+ 31480,
+ 35370,
+ 37169,
+ 40710,
+ 43190,
+ 46980,
+ 49050,
+ 52589,
+ 55170,
+ 57100,
+ 59330,
+ 60949,
+ 65450,
+ 69710,
+ 73479,
+ 75100,
+ 77600,
+ 78789,
+ 81330,
+ 82580,
+ 83340
+ ],
+ "text": [
+ null,
+ "SPEAKER 1: In this next segment,\nI'd like to show you",
+ "a little demo of a set of lumped\nelements that you will",
+ "find in 6002x and in building\nelectronic circuits and",
+ "systems in general.",
+ "And these lumped elements have\nbehavior that is completely",
+ "captured by their V-I\nrelationship.",
+ "I also want to point out that\nthe behavior of these lumped",
+ "elements is captured by the\nV-I relationship only for",
+ "those sorts of questions that\nwe, as electrical engineers,",
+ "would like to ask.",
+ "Understand that the V-I relation\ndoesn't capture all",
+ "the possible behaviors\nof the element.",
+ "It only captures those possible\nbehaviors that we, as",
+ "EEs, generally like to ask.",
+ "Once I've shown you these basic\nV-I relationships, we",
+ "will do some other fun stuff.",
+ "I'm going to show you some\nexamples of behavior that the",
+ "V-I relationship will\nnot capture.",
+ "We'll show you what\nwe can predict.",
+ "For example, we'll\nshow you how the",
+ "resistors don't work sometimes.",
+ "And sometimes we will show you\nanother example of a cucumber",
+ "pickle that may behave in ways\nthat you don't quite expect",
+ "and whose behavior need not\nnecessarily be captured by",
+ "your V-I relationship.",
+ "So I don't want you to go around\nsaying that you don't",
+ "have any fun in this course.",
+ "So hopefully, you will\nenjoy what you are",
+ "going to see ahead.",
+ null
+ ]
+}
\ No newline at end of file
diff --git a/common/test/data/full/to_delete b/common/test/data/full/to_delete
new file mode 100644
index 0000000000..e36b7d8744
--- /dev/null
+++ b/common/test/data/full/to_delete
@@ -0,0 +1,339 @@
+chapter/Week_10
+chapter/Week_11
+chapter/Week_12
+chapter/Week_4
+chapter/Week_5
+chapter/Week_6
+chapter/Week_7
+chapter/Week_8
+chapter/Week_9
+html/Lab_10B_RC_Filters_Experiment
+html/Lab_5B_Mosfet_Amplifier_Experiment
+html/Week_10_Tutorials
+html/Week_11_Tutorials
+html/Week_12_Tutorials
+html/Week_4_Tutorials
+html/Week_5_Tutorials
+html/Week_6_Tutorials
+html/Week_7_Tutorials
+html/Week_8_Tutorials
+html/Week_9_Tutorials
+problem/Capacitors_and_Energy_Storage
+problem/Curve_Tracer
+problem/H10P1_Magnitude_and_Angle
+problem/H10P2_Impedances
+problem/H10P3_An_L_Network
+problem/H11P1_LC_Tank
+problem/H11P2_Scope_Probe
+problem/H11P3_A_Step_in_Time
+problem/H12P1_Current_Source
+problem/H12P2_Measuring_Light_
+problem/H12P3_Opamps_and_Filter_Design
+problem/H4P0_Vacuum_Diode
+problem/H4P1_Zener_Regulator
+problem/H5P1_Source_Follower_Large_Signal
+problem/H5P2_Source_Follower_Small_Signal
+problem/H6P1_The_NewFET_device
+problem/H6P2_Phase_Inverter
+problem/H6P3_Series_and_Parallel_Capacitors
+problem/H6P4_Switched_RC
+problem/H7P1_Series_and_Parallel_Inductors
+problem/H7P2_The_Curse_of_Lead_Inductance
+problem/H8P1_Charging_an_Inductor
+problem/H8P2_Physiological_Model
+problem/H8P3_Memory
+problem/H9P1_Response_to_a_Delayed_Impulse
+problem/H9P2_An_L_Network
+problem/H9P3_Designing_a_Shock_Absorber
+problem/Impedance_Frequency_Response
+problem/Mosfet_Amplifier
+problem/Op_Amps
+problem/Propagation_Delay
+problem/Resonance
+problem/S10E1_Incremental_Voltage
+problem/S10E2_two_terminal_connection
+problem/S10E3_Small_Signal_Amplifier
+problem/S14E1_Response_to_step_down
+problem/S14E2_Rise_Time
+problem/S14E3_Fall_Time_Constant
+problem/S14E4_Fall_Time
+problem/S15E1_Review_A_Step_Up
+problem/S15E2_Review_A_Step_Down
+problem/S15E3_Review_A_Pulse_is_Step_Up_then_Step_Down
+problem/S15E4_Area
+problem/S15E5_Initial_Conditions
+problem/S16E1_Charging_and_Discharging
+problem/S16E2_Time_to_Decay
+problem/S19E1_Trigonometry_Isn_t_So_Bad
+problem/S19E2_Exponentials_are_Nice
+problem/S19E3_Complex_Numbers
+problem/S19E4_Magnitudes_and_Angles
+problem/S20E1_Inductor_Impedance
+problem/S20E2_RC_voltage_divider
+problem/S20E3_LCR_voltage_divider
+problem/S20E4_LCR_voltage_divider_frequency_limits
+problem/S21E2_LR_filter
+problem/S21E3_Thevenin_Tank
+problem/S21E4_AM_Radio_Tuning
+problem/S22E1_Which_output_
+problem/S22E2_The_filter_is_ringing
+problem/S23E2_Inverting_Amplifier_analysis
+problem/S23E3_L23AmplifierInputResistance
+problem/S24E1_Summing_Amplifier
+problem/S24E2_Difference_Amplifier
+problem/S24E3_Inverting_Amplifier_Generalized
+problem/S24E4_Generalization_to_impedances
+problem/S24E5_Generalization_to_nonlinear_elements
+problem/S7E1_Linearization
+problem/S7E2_Graphs
+problem/S7E3_Linearization
+problem/S8E0_Dependent_Source
+problem/S8E1_Dependent_Current_Source
+problem/S8E2_Dependent_Voltage_Source
+problem/S9E1_MOSFET_model
+problem/S9E2_Amplifier_1
+problem/S9E3_MOSFET_Amplifier_2
+sequential/Dependent_Sources_and_Amplifiers
+sequential/Filters
+sequential/Incremental_Analysis
+sequential/MOSFET_Amplifiers_Small_signal_model
+sequential/MOSFETs_Large_Signals
+sequential/Operational_Amplifier_Circuits
+sequential/Ramps_Steps_and_Impulses
+sequential/Sinusoidal_Steady_State
+sequential/Speed_of_Digital_Circuits
+sequential/State_and_Memory
+sequential/The_Impedance_Model
+sequential/The_Operational_Amplifier_Abstraction
+sequential/Time_Domain_Versus_Frequency_Domain_Analysis
+vertical/vertical_1002
+vertical/vertical_1006
+vertical/vertical_1010
+vertical/vertical_1014
+vertical/vertical_1018
+vertical/vertical_1022
+vertical/vertical_1026
+vertical/vertical_1030
+vertical/vertical_1034
+vertical/vertical_1038
+vertical/vertical_1042
+vertical/vertical_1046
+vertical/vertical_1050
+vertical/vertical_1054
+vertical/vertical_1058
+vertical/vertical_1062
+vertical/vertical_1069
+vertical/vertical_1073
+vertical/vertical_1077
+vertical/vertical_1081
+vertical/vertical_1085
+vertical/vertical_1089
+vertical/vertical_1093
+vertical/vertical_1097
+vertical/vertical_1100
+vertical/vertical_1104
+vertical/vertical_1108
+vertical/vertical_1112
+vertical/vertical_1320
+vertical/vertical_1324
+vertical/vertical_1328
+vertical/vertical_1332
+vertical/vertical_1336
+vertical/vertical_1340
+vertical/vertical_1344
+vertical/vertical_1348
+vertical/vertical_1352
+vertical/vertical_1356
+vertical/vertical_1360
+vertical/vertical_1364
+vertical/vertical_1368
+vertical/vertical_1373
+vertical/vertical_1377
+vertical/vertical_1381
+vertical/vertical_1385
+vertical/vertical_1389
+vertical/vertical_1393
+vertical/vertical_1403
+vertical/vertical_1407
+vertical/vertical_1411
+vertical/vertical_1415
+vertical/vertical_1419
+vertical/vertical_1423
+vertical/vertical_1427
+vertical/vertical_1431
+vertical/vertical_1435
+vertical/vertical_1439
+vertical/vertical_1443
+vertical/vertical_1447
+vertical/vertical_1451
+vertical/vertical_1455
+vertical/vertical_1459
+vertical/vertical_1463
+vertical/vertical_1467
+vertical/vertical_1471
+vertical/vertical_1479
+vertical/vertical_1483
+vertical/vertical_1487
+vertical/vertical_1491
+vertical/vertical_1495
+vertical/vertical_1499
+vertical/vertical_1503
+vertical/vertical_1507
+vertical/vertical_1511
+vertical/vertical_1515
+vertical/vertical_1519
+vertical/vertical_1523
+vertical/vertical_1527
+vertical/vertical_1531
+vertical/vertical_1535
+vertical/vertical_1539
+vertical/vertical_1543
+vertical/vertical_1547
+vertical/vertical_1551
+vertical/vertical_1555
+vertical/vertical_1559
+vertical/vertical_1563
+vertical/vertical_1566
+vertical/vertical_1570
+vertical/vertical_1574
+vertical/vertical_1578
+vertical/vertical_1582
+vertical/vertical_1586
+vertical/vertical_1590
+vertical/vertical_1594
+vertical/vertical_1598
+vertical/vertical_1603
+vertical/vertical_1607
+vertical/vertical_1611
+vertical/vertical_1615
+vertical/vertical_1619
+vertical/vertical_1623
+vertical/vertical_1627
+vertical/vertical_1631
+vertical/vertical_1635
+vertical/vertical_1639
+vertical/vertical_1643
+vertical/vertical_1647
+vertical/vertical_1651
+vertical/vertical_1655
+vertical/vertical_1659
+vertical/vertical_1663
+vertical/vertical_1667
+vertical/vertical_1671
+vertical/vertical_1675
+vertical/vertical_1679
+vertical/vertical_1683
+vertical/vertical_1687
+vertical/vertical_1691
+vertical/vertical_1695
+vertical/vertical_1699
+vertical/vertical_1703
+vertical/vertical_1707
+vertical/vertical_1711
+vertical/vertical_1715
+vertical/vertical_1719
+vertical/vertical_1723
+vertical/vertical_1727
+vertical/vertical_1731
+vertical/vertical_1735
+vertical/vertical_1739
+vertical/vertical_1743
+vertical/vertical_1747
+vertical/vertical_1751
+vertical/vertical_1755
+vertical/vertical_1759
+vertical/vertical_1763
+vertical/vertical_1767
+vertical/vertical_431
+vertical/vertical_435
+vertical/vertical_439
+vertical/vertical_443
+vertical/vertical_447
+vertical/vertical_451
+vertical/vertical_455
+vertical/vertical_459
+vertical/vertical_463
+vertical/vertical_469
+vertical/vertical_475
+vertical/vertical_479
+vertical/vertical_483
+vertical/vertical_487
+vertical/vertical_491
+vertical/vertical_495
+vertical/vertical_504
+vertical/vertical_508
+vertical/vertical_512
+vertical/vertical_516
+vertical/vertical_520
+vertical/vertical_524
+vertical/vertical_530
+vertical/vertical_534
+vertical/vertical_538
+vertical/vertical_542
+vertical/vertical_546
+vertical/vertical_550
+vertical/vertical_554
+vertical/vertical_558
+vertical/vertical_562
+vertical/vertical_566
+vertical/vertical_570
+vertical/vertical_577
+vertical/vertical_581
+vertical/vertical_585
+vertical/vertical_589
+vertical/vertical_593
+vertical/vertical_597
+vertical/vertical_601
+vertical/vertical_605
+vertical/vertical_609
+vertical/vertical_613
+vertical/vertical_617
+vertical/vertical_621
+vertical/vertical_625
+vertical/vertical_629
+vertical/vertical_633
+vertical/vertical_637
+vertical/vertical_641
+vertical/vertical_645
+vertical/vertical_649
+vertical/vertical_653
+vertical/vertical_660
+vertical/vertical_664
+vertical/vertical_668
+vertical/vertical_672
+vertical/vertical_676
+vertical/vertical_680
+vertical/vertical_684
+vertical/vertical_688
+vertical/vertical_692
+vertical/vertical_696
+vertical/vertical_700
+vertical/vertical_704
+vertical/vertical_886
+vertical/vertical_890
+vertical/vertical_894
+vertical/vertical_898
+vertical/vertical_902
+vertical/vertical_906
+vertical/vertical_910
+vertical/vertical_914
+vertical/vertical_918
+vertical/vertical_922
+vertical/vertical_926
+vertical/vertical_930
+vertical/vertical_938
+vertical/vertical_942
+vertical/vertical_946
+vertical/vertical_950
+vertical/vertical_954
+vertical/vertical_958
+vertical/vertical_962
+vertical/vertical_966
+vertical/vertical_970
+vertical/vertical_974
+vertical/vertical_978
+vertical/vertical_982
+vertical/vertical_986
+vertical/vertical_990
+vertical/vertical_994
+vertical/vertical_998
diff --git a/common/test/data/full/vertical/vertical_58.xml b/common/test/data/full/vertical/vertical_58.xml
new file mode 100644
index 0000000000..7eba0f9dad
--- /dev/null
+++ b/common/test/data/full/vertical/vertical_58.xml
@@ -0,0 +1,12 @@
+
+
+
+ discuss
+
+
+ book
+
+
+ slides
+
+
diff --git a/common/test/data/full/vertical/vertical_89.xml b/common/test/data/full/vertical/vertical_89.xml
new file mode 100644
index 0000000000..06b13846d2
--- /dev/null
+++ b/common/test/data/full/vertical/vertical_89.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ discuss
+
+
+ book
+
+
+ slides
+
+
diff --git a/common/test/data/full/vertical/vertical_94.xml b/common/test/data/full/vertical/vertical_94.xml
new file mode 100644
index 0000000000..59fe9ddb07
--- /dev/null
+++ b/common/test/data/full/vertical/vertical_94.xml
@@ -0,0 +1,12 @@
+
+
+
+ discuss
+
+
+ book
+
+
+ slides
+
+
diff --git a/common/test/data/full/vertical/vertical_98.xml b/common/test/data/full/vertical/vertical_98.xml
new file mode 100644
index 0000000000..b91f3caaaa
--- /dev/null
+++ b/common/test/data/full/vertical/vertical_98.xml
@@ -0,0 +1,12 @@
+
+
+
+ discuss
+
+
+ book
+
+
+ slides
+
+
diff --git a/common/test/data/toy/course.xml b/common/test/data/toy/course.xml
index ecac9a4776..645fbd7af8 100644
--- a/common/test/data/toy/course.xml
+++ b/common/test/data/toy/course.xml
@@ -1,11 +1,9 @@
-
-
+
+
+
-
+
diff --git a/common/test/data/toy/html/Lab2A.html b/common/test/data/toy/html/Lab2A.html
new file mode 100644
index 0000000000..7fe52cc1be
--- /dev/null
+++ b/common/test/data/toy/html/Lab2A.html
@@ -0,0 +1,105 @@
+
+
+Lab 2A: Superposition Experiment
+
+Note: This part of the lab is just to develop your intuition about
+superposition. There are no responses that need to be checked.
+
+ Circuits with multiple sources can be hard to analyze as-is. For example, what is the voltage
+between the two terminals on the right of Figure 1?
+
+
+
+Figure 1. Example multi-source circuit
+
+
+ We can use superposition to make the analysis much easier.
+The circuit in Figure 1 can be decomposed into two separate
+subcircuits: one involving only the voltage source and one involving only the
+current source. We'll analyze each circuit separately and combine the
+results using superposition. Recall that to decompose a circuit for
+analysis, we'll pick each source in turn and set all the other sources
+to zero (i.e., voltage sources become short circuits and current
+sources become open circuits). The circuit above has two sources, so
+the decomposition produces two subcircuits, as shown in Figure 2.
+
+
+
+
+(a) Subcircuit for analyzing contribution of voltage source
+
+
+(b) Subcircuit for analyzing contribution of current source
+
+ Figure 2. Decomposition of Figure 1 into subcircuits
+
+
+ Let's use the DC analysis capability of the schematic tool to see superposition
+in action. The sliders below control the resistances of R1, R2, R3 and R4 in all
+the diagrams. As you move the sliders, the schematic tool will adjust the appropriate
+resistance, perform a DC analysis and display the node voltages on the diagrams. Here's
+what you want to observe as you play with the sliders:
+
+
+The voltage for a node in Figure 1 is the sum of the voltages for
+that node in Figures 2(a) and 2(b), just as predicted by
+superposition. (Note that due to round-off in the display of the
+voltages, the sum of the displayed voltages in Figure 2 may only be within
+.01 of the voltages displayed in Figure 1.)
+
+
+
+
+
+
+
+
+ R1
+
+
+
+
+
+ R2
+
+
+
+
+
+ R3
+
+
+
+
+
+ R4
+
+
+
+
+
+
+
diff --git a/create-dev-env.sh b/create-dev-env.sh
index 5bd7971629..91f9b81931 100755
--- a/create-dev-env.sh
+++ b/create-dev-env.sh
@@ -36,7 +36,7 @@ usage() {
Usage: $PROG [-c] [-v] [-h]
-c compile scipy and numpy
- -s --system-site-packages for virtualenv
+ -s do _not_ set --no-site-packages for virtualenv
-v set -x + spew
-h this
@@ -254,9 +254,9 @@ source $RUBY_DIR/scripts/rvm
# skip the intro
LESS="-E" rvm install $RUBY_VER
if [[ -n $systempkgs ]]; then
- virtualenv --system-site-packages "$PYTHON_DIR"
-else
virtualenv "$PYTHON_DIR"
+else
+ virtualenv --no-site-packages "$PYTHON_DIR"
fi
source $PYTHON_DIR/bin/activate
output "Installing gem bundler"
diff --git a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py
index 7755bb8f32..7d384103ab 100644
--- a/lms/djangoapps/certificates/management/commands/ungenerated_certs.py
+++ b/lms/djangoapps/certificates/management/commands/ungenerated_certs.py
@@ -1,20 +1,22 @@
from django.utils.simplejson import dumps
from django.core.management.base import BaseCommand, CommandError
from certificates.models import GeneratedCertificate
+
+
class Command(BaseCommand):
- help = """
- This command finds all GeneratedCertificate objects that do not have a
- certificate generated. These come into being when a user requests a
- certificate, or when grade_all_students is called (for pre-generating
+ help = """
+ This command finds all GeneratedCertificate objects that do not have a
+ certificate generated. These come into being when a user requests a
+ certificate, or when grade_all_students is called (for pre-generating
certificates).
-
+
It returns a json formatted list of users and their user ids
"""
def handle(self, *args, **options):
users = GeneratedCertificate.objects.filter(
- download_url = None )
+ download_url=None)
user_output = [{'user_id':user.user_id, 'name':user.name}
for user in users]
self.stdout.write(dumps(user_output) + "\n")
diff --git a/lms/djangoapps/certificates/migrations/0001_added_generatedcertificates.py b/lms/djangoapps/certificates/migrations/0001_added_generatedcertificates.py
index 0dc76b31f8..094a439085 100644
--- a/lms/djangoapps/certificates/migrations/0001_added_generatedcertificates.py
+++ b/lms/djangoapps/certificates/migrations/0001_added_generatedcertificates.py
@@ -90,4 +90,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['certificates']
\ No newline at end of file
+ complete_apps = ['certificates']
diff --git a/lms/djangoapps/certificates/migrations/0002_auto__add_field_generatedcertificate_download_url.py b/lms/djangoapps/certificates/migrations/0002_auto__add_field_generatedcertificate_download_url.py
index ec1abd0154..0019a0c491 100644
--- a/lms/djangoapps/certificates/migrations/0002_auto__add_field_generatedcertificate_download_url.py
+++ b/lms/djangoapps/certificates/migrations/0002_auto__add_field_generatedcertificate_download_url.py
@@ -88,4 +88,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['certificates']
\ No newline at end of file
+ complete_apps = ['certificates']
diff --git a/lms/djangoapps/certificates/migrations/0003_auto__add_field_generatedcertificate_enabled.py b/lms/djangoapps/certificates/migrations/0003_auto__add_field_generatedcertificate_enabled.py
index 880494d226..8c1150f497 100644
--- a/lms/djangoapps/certificates/migrations/0003_auto__add_field_generatedcertificate_enabled.py
+++ b/lms/djangoapps/certificates/migrations/0003_auto__add_field_generatedcertificate_enabled.py
@@ -89,4 +89,4 @@ class Migration(SchemaMigration):
}
}
- complete_apps = ['certificates']
\ No newline at end of file
+ complete_apps = ['certificates']
diff --git a/lms/djangoapps/certificates/migrations/0004_auto__add_field_generatedcertificate_graded_certificate_id__add_field_.py b/lms/djangoapps/certificates/migrations/0004_auto__add_field_generatedcertificate_graded_certificate_id__add_field_.py
index ffa6ec7175..6d7b41a1d6 100644
--- a/lms/djangoapps/certificates/migrations/0004_auto__add_field_generatedcertificate_graded_certificate_id__add_field_.py
+++ b/lms/djangoapps/certificates/migrations/0004_auto__add_field_generatedcertificate_graded_certificate_id__add_field_.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Adding field 'GeneratedCertificate.graded_certificate_id'
db.add_column('certificates_generatedcertificate', 'graded_certificate_id', self.gf('django.db.models.fields.CharField')(max_length=32, null=True), keep_default=False)
@@ -17,9 +18,8 @@ class Migration(SchemaMigration):
# Adding field 'GeneratedCertificate.grade'
db.add_column('certificates_generatedcertificate', 'grade', self.gf('django.db.models.fields.CharField')(max_length=5, null=True), keep_default=False)
-
def backwards(self, orm):
-
+
# Deleting field 'GeneratedCertificate.graded_certificate_id'
db.delete_column('certificates_generatedcertificate', 'graded_certificate_id')
@@ -29,7 +29,6 @@ class Migration(SchemaMigration):
# Deleting field 'GeneratedCertificate.grade'
db.delete_column('certificates_generatedcertificate', 'grade')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/lms/djangoapps/certificates/migrations/0005_auto__add_field_generatedcertificate_name.py b/lms/djangoapps/certificates/migrations/0005_auto__add_field_generatedcertificate_name.py
index c463145504..9b3660a5b3 100644
--- a/lms/djangoapps/certificates/migrations/0005_auto__add_field_generatedcertificate_name.py
+++ b/lms/djangoapps/certificates/migrations/0005_auto__add_field_generatedcertificate_name.py
@@ -4,20 +4,19 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Adding field 'GeneratedCertificate.name'
db.add_column('certificates_generatedcertificate', 'name', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False)
-
def backwards(self, orm):
-
+
# Deleting field 'GeneratedCertificate.name'
db.delete_column('certificates_generatedcertificate', 'name')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/lms/djangoapps/certificates/migrations/0006_auto__chg_field_generatedcertificate_certificate_id.py b/lms/djangoapps/certificates/migrations/0006_auto__chg_field_generatedcertificate_certificate_id.py
index c046c4f901..947f0f967d 100644
--- a/lms/djangoapps/certificates/migrations/0006_auto__chg_field_generatedcertificate_certificate_id.py
+++ b/lms/djangoapps/certificates/migrations/0006_auto__chg_field_generatedcertificate_certificate_id.py
@@ -4,20 +4,19 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Changing field 'GeneratedCertificate.certificate_id'
db.alter_column('certificates_generatedcertificate', 'certificate_id', self.gf('django.db.models.fields.CharField')(max_length=32, null=True))
-
def backwards(self, orm):
-
+
# Changing field 'GeneratedCertificate.certificate_id'
db.alter_column('certificates_generatedcertificate', 'certificate_id', self.gf('django.db.models.fields.CharField')(default=None, max_length=32))
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/lms/djangoapps/certificates/migrations/0007_auto__add_revokedcertificate.py b/lms/djangoapps/certificates/migrations/0007_auto__add_revokedcertificate.py
index ee98eee990..03f2548679 100644
--- a/lms/djangoapps/certificates/migrations/0007_auto__add_revokedcertificate.py
+++ b/lms/djangoapps/certificates/migrations/0007_auto__add_revokedcertificate.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Adding model 'RevokedCertificate'
db.create_table('certificates_revokedcertificate', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
@@ -23,13 +24,11 @@ class Migration(SchemaMigration):
))
db.send_create_signal('certificates', ['RevokedCertificate'])
-
def backwards(self, orm):
-
+
# Deleting model 'RevokedCertificate'
db.delete_table('certificates_revokedcertificate')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py
index d3323eed77..5815db64ca 100644
--- a/lms/djangoapps/certificates/models.py
+++ b/lms/djangoapps/certificates/models.py
@@ -7,7 +7,7 @@ from django.db import models
'''
Certificates are created for a student and an offering of a course.
-When a certificate is generated, a unique ID is generated so that
+When a certificate is generated, a unique ID is generated so that
the certificate can be verified later. The ID is a UUID4, so that
it can't be easily guessed and so that it is unique. Even though
we save these generated certificates (for later verification), we
@@ -15,7 +15,7 @@ also record the UUID so that if we regenerate the certificate it
will have the same UUID.
If certificates are being generated on the fly, a GeneratedCertificate
-should be created with the user, certificate_id, and enabled set
+should be created with the user, certificate_id, and enabled set
when a student requests a certificate. When the certificate has been
generated, the download_url should be set.
@@ -26,119 +26,119 @@ needs to be set to true.
'''
+
class GeneratedCertificate(models.Model):
user = models.ForeignKey(User, db_index=True)
# This is the name at the time of request
name = models.CharField(blank=True, max_length=255)
-
+
certificate_id = models.CharField(max_length=32, null=True, default=None)
graded_certificate_id = models.CharField(max_length=32, null=True, default=None)
-
+
download_url = models.CharField(max_length=128, null=True)
graded_download_url = models.CharField(max_length=128, null=True)
-
+
grade = models.CharField(max_length=5, null=True)
-
+
# enabled should only be true if the student has earned a grade in the course
# The student must have a grade and request a certificate for enabled to be True
enabled = models.BooleanField(default=False)
-
+
+
class RevokedCertificate(models.Model):
"""
This model is for when a GeneratedCertificate must be regenerated. This model
contains all the same fields, to store a record of what the GeneratedCertificate
was before it was revoked (at which time all of it's information can change when
it is regenerated).
-
+
GeneratedCertificate may be deleted once they are revoked, and then created again.
For this reason, the only link between a GeneratedCertificate and RevokedCertificate
is that they share the same user.
"""
####-------------------New Fields--------------------####
explanation = models.TextField(blank=True)
-
+
####---------Fields from GeneratedCertificate---------####
user = models.ForeignKey(User, db_index=True)
# This is the name at the time of request
name = models.CharField(blank=True, max_length=255)
-
+
certificate_id = models.CharField(max_length=32, null=True, default=None)
graded_certificate_id = models.CharField(max_length=32, null=True, default=None)
-
+
download_url = models.CharField(max_length=128, null=True)
graded_download_url = models.CharField(max_length=128, null=True)
-
+
grade = models.CharField(max_length=5, null=True)
-
+
enabled = models.BooleanField(default=False)
-
+
def revoke_certificate(certificate, explanation):
"""
This method takes a GeneratedCertificate. It records its information from the certificate
- into a RevokedCertificate, and then marks the certificate as needing regenerating.
+ into a RevokedCertificate, and then marks the certificate as needing regenerating.
When the new certificiate is regenerated it will have new IDs and download URLS.
-
- Once this method has been called, it is safe to delete the certificate, or modify the
+
+ Once this method has been called, it is safe to delete the certificate, or modify the
certificate's name or grade until it has been generated again.
"""
- revoked = RevokedCertificate( user = certificate.user,
- name = certificate.name,
- certificate_id = certificate.certificate_id,
- graded_certificate_id = certificate.graded_certificate_id,
- download_url = certificate.download_url,
- graded_download_url = certificate.graded_download_url,
- grade = certificate.grade,
- enabled = certificate.enabled)
-
+ revoked = RevokedCertificate(user=certificate.user,
+ name=certificate.name,
+ certificate_id=certificate.certificate_id,
+ graded_certificate_id=certificate.graded_certificate_id,
+ download_url=certificate.download_url,
+ graded_download_url=certificate.graded_download_url,
+ grade=certificate.grade,
+ enabled=certificate.enabled)
+
revoked.explanation = explanation
-
+
certificate.certificate_id = None
certificate.graded_certificate_id = None
certificate.download_url = None
certificate.graded_download_url = None
-
+
certificate.save()
revoked.save()
-
-
def certificate_state_for_student(student, grade):
'''
This returns a dictionary with a key for state, and other information. The state is one of the
following:
-
+
unavailable - A student is not eligible for a certificate.
requestable - A student is eligible to request a certificate
generating - A student has requested a certificate, but it is not generated yet.
downloadable - The certificate has been requested and is available for download.
-
+
If the state is "downloadable", the dictionary also contains "download_url" and "graded_download_url".
-
+
'''
-
+
if grade:
#TODO: Remove the following after debugging
if settings.DEBUG_SURVEY:
- return {'state' : 'requestable' }
-
+ return {'state': 'requestable'}
+
try:
- generated_certificate = GeneratedCertificate.objects.get(user = student)
+ generated_certificate = GeneratedCertificate.objects.get(user=student)
if generated_certificate.enabled:
if generated_certificate.download_url:
- return {'state' : 'downloadable',
- 'download_url' : generated_certificate.download_url,
- 'graded_download_url' : generated_certificate.graded_download_url}
+ return {'state': 'downloadable',
+ 'download_url': generated_certificate.download_url,
+ 'graded_download_url': generated_certificate.graded_download_url}
else:
- return {'state' : 'generating'}
+ return {'state': 'generating'}
else:
# If enabled=False, it may have been pre-generated but not yet requested
# Our output will be the same as if the GeneratedCertificate did not exist
pass
except GeneratedCertificate.DoesNotExist:
pass
- return {'state' : 'requestable'}
+ return {'state': 'requestable'}
else:
# No grade, no certificate. No exceptions
- return {'state' : 'unavailable'}
+ return {'state': 'unavailable'}
diff --git a/lms/djangoapps/certificates/views.py b/lms/djangoapps/certificates/views.py
index 354e8cc4d4..6341133c52 100644
--- a/lms/djangoapps/certificates/views.py
+++ b/lms/djangoapps/certificates/views.py
@@ -18,76 +18,74 @@ from student.models import UserProfile
log = logging.getLogger("mitx.certificates")
+
@login_required
def certificate_request(request):
''' Attempt to send a certificate. '''
if not settings.END_COURSE_ENABLED:
raise Http404
-
+
if request.method == "POST":
honor_code_verify = request.POST.get('cert_request_honor_code_verify', 'false')
name_verify = request.POST.get('cert_request_name_verify', 'false')
id_verify = request.POST.get('cert_request_id_verify', 'false')
error = ''
-
+
def return_error(error):
- return HttpResponse(json.dumps({'success':False,
- 'error': error }))
-
+ return HttpResponse(json.dumps({'success': False,
+ 'error': error}))
+
if honor_code_verify != 'true':
error += 'Please verify that you have followed the honor code to receive a certificate. '
-
+
if name_verify != 'true':
error += 'Please verify that your name is correct to receive a certificate. '
-
+
if id_verify != 'true':
error += 'Please certify that you understand the unique ID on the certificate. '
-
+
if len(error) > 0:
return return_error(error)
-
+
survey_response = record_exit_survey(request, internal_request=True)
if not survey_response['success']:
- return return_error( survey_response['error'] )
-
+ return return_error(survey_response['error'])
+
grade = None
student_gradesheet = grades.grade_sheet(request.user)
grade = student_gradesheet['grade']
-
+
if not grade:
return return_error('You have not earned a grade in this course. ')
-
+
generate_certificate(request.user, grade)
-
- return HttpResponse(json.dumps({'success':True}))
-
+
+ return HttpResponse(json.dumps({'success': True}))
+
else:
#This is not a POST, we should render the page with the form
-
+
grade_sheet = grades.grade_sheet(request.user)
certificate_state = certificate_state_for_student(request.user, grade_sheet['grade'])
-
+
if certificate_state['state'] != "requestable":
return redirect("/profile")
-
+
user_info = UserProfile.objects.get(user=request.user)
-
+
took_survey = student_took_survey(user_info)
if settings.DEBUG_SURVEY:
took_survey = False
survey_list = []
if not took_survey:
survey_list = exit_survey_list_for_student(request.user)
-
-
- context = {'certificate_state' : certificate_state,
- 'took_survey' : took_survey,
- 'survey_list' : survey_list,
- 'name' : user_info.name }
-
-
- return render_to_response('cert_request.html', context)
+ context = {'certificate_state': certificate_state,
+ 'took_survey': took_survey,
+ 'survey_list': survey_list,
+ 'name': user_info.name}
+
+ return render_to_response('cert_request.html', context)
# This method should only be called if the user has a grade and has requested a certificate
@@ -96,11 +94,11 @@ def generate_certificate(user, grade):
# states for a GeneratedCertificate object
if grade and user.is_active:
generated_certificate = None
-
+
try:
- generated_certificate = GeneratedCertificate.objects.get(user = user)
+ generated_certificate = GeneratedCertificate.objects.get(user=user)
except GeneratedCertificate.DoesNotExist:
- generated_certificate = GeneratedCertificate(user = user)
+ generated_certificate = GeneratedCertificate(user=user)
generated_certificate.enabled = True
if generated_certificate.graded_download_url and (generated_certificate.grade != grade):
@@ -114,8 +112,8 @@ def generate_certificate(user, grade):
ungraded_dl_url=generated_certificate.download_url,
userid=user.id))
revoke_certificate(generated_certificate, "The grade on this certificate may be inaccurate.")
-
- user_name = UserProfile.objects.get(user = user).name
+
+ user_name = UserProfile.objects.get(user=user).name
if generated_certificate.download_url and (generated_certificate.name != user_name):
log.critical(u"A Certificate has been pre-generated with the name of "
"{gen_name} but current name is {user_name} (user id is "
@@ -128,22 +126,21 @@ def generate_certificate(user, grade):
userid=user.id))
revoke_certificate(generated_certificate, "The name on this certificate may be inaccurate.")
-
generated_certificate.grade = grade
generated_certificate.name = user_name
generated_certificate.save()
-
+
certificate_id = generated_certificate.certificate_id
-
+
log.debug("Generating certificate for " + str(user.username) + " with ID: " + str(certificate_id))
-
+
# TODO: If the certificate was pre-generated, send the email that it is ready to download
if certificate_state_for_student(user, grade)['state'] == "downloadable":
- subject = render_to_string('emails/certificate_ready_subject.txt',{})
+ subject = render_to_string('emails/certificate_ready_subject.txt', {})
subject = ''.join(subject.splitlines())
- message = render_to_string('emails/certificate_ready.txt',{})
-
- res=send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email,])
-
+ message = render_to_string('emails/certificate_ready.txt', {})
+
+ res = send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email, ])
+
else:
log.warning("Asked to generate a certificate for student " + str(user.username) + " but with a grade of " + str(grade) + " and active status " + str(user.is_active))
diff --git a/lms/djangoapps/circuit/models.py b/lms/djangoapps/circuit/models.py
index cc103e1af8..21a70bcb25 100644
--- a/lms/djangoapps/circuit/models.py
+++ b/lms/djangoapps/circuit/models.py
@@ -3,10 +3,11 @@ import uuid
from django.db import models
from django.contrib.auth.models import User
+
class ServerCircuit(models.Model):
- # Later, add owner, who can edit, part of what app, etc.
+ # Later, add owner, who can edit, part of what app, etc.
name = models.CharField(max_length=32, unique=True, db_index=True)
schematic = models.TextField(blank=True)
def __unicode__(self):
- return self.name+":"+self.schematic[:8]
+ return self.name + ":" + self.schematic[:8]
diff --git a/lms/djangoapps/circuit/views.py b/lms/djangoapps/circuit/views.py
index 96fcc83115..9711e0648c 100644
--- a/lms/djangoapps/circuit/views.py
+++ b/lms/djangoapps/circuit/views.py
@@ -11,8 +11,9 @@ from mitxmako.shortcuts import render_to_response, render_to_string
from models import ServerCircuit
+
def circuit_line(circuit):
- ''' Returns string for an appropriate input element for a circuit.
+ ''' Returns string for an appropriate input element for a circuit.
TODO: Rename. '''
if not circuit.isalnum():
raise Http404()
@@ -28,10 +29,11 @@ def circuit_line(circuit):
circuit_line.set('width', '640')
circuit_line.set('height', '480')
circuit_line.set('name', 'schematic')
- circuit_line.set('id', 'schematic_'+circuit)
- circuit_line.set('value', schematic) # We do it this way for security -- guarantees users cannot put funny stuff in schematic.
+ circuit_line.set('id', 'schematic_' + circuit)
+ circuit_line.set('value', schematic) # We do it this way for security -- guarantees users cannot put funny stuff in schematic.
return xml.etree.ElementTree.tostring(circuit_line)
+
def edit_circuit(request, circuit):
try:
sc = ServerCircuit.objects.get(name=circuit)
@@ -40,11 +42,12 @@ def edit_circuit(request, circuit):
if not circuit.isalnum():
raise Http404()
- response = render_to_response('edit_circuit.html', {'name':circuit,
- 'circuit_line':circuit_line(circuit)})
+ response = render_to_response('edit_circuit.html', {'name': circuit,
+ 'circuit_line': circuit_line(circuit)})
response['Cache-Control'] = 'no-cache'
return response
+
def save_circuit(request, circuit):
if not circuit.isalnum():
raise Http404()
@@ -63,4 +66,3 @@ def save_circuit(request, circuit):
response = HttpResponse(json_str, mimetype='application/json')
response['Cache-Control'] = 'no-cache'
return response
-
diff --git a/lms/djangoapps/courseware/course_settings.py b/lms/djangoapps/courseware/course_settings.py
index d9876677d6..5b2348bee6 100644
--- a/lms/djangoapps/courseware/course_settings.py
+++ b/lms/djangoapps/courseware/course_settings.py
@@ -1,13 +1,13 @@
"""
Course settings module. All settings in the global_settings are
first applied, and then any settings in the settings.DATA_DIR/course_settings.json
-are applied. A setting must be in ALL_CAPS.
-
+are applied. A setting must be in ALL_CAPS.
+
Settings are used by calling
from courseware.course_settings import course_settings
-Note that courseware.course_settings.course_settings is not a module -- it's an object. So
+Note that courseware.course_settings.course_settings is not a module -- it's an object. So
importing individual settings is not possible:
from courseware.course_settings.course_settings import GRADER # This won't work.
@@ -24,69 +24,67 @@ log = logging.getLogger("mitx.courseware")
global_settings_json = """
{
- "GRADER" : [
- {
- "type" : "Homework",
- "min_count" : 12,
- "drop_count" : 2,
- "short_label" : "HW",
- "weight" : 0.15
- },
- {
- "type" : "Lab",
- "min_count" : 12,
- "drop_count" : 2,
- "category" : "Labs",
- "weight" : 0.15
- },
- {
- "type" : "Midterm",
- "name" : "Midterm Exam",
- "short_label" : "Midterm",
- "weight" : 0.3
- },
- {
- "type" : "Final",
- "name" : "Final Exam",
- "short_label" : "Final",
- "weight" : 0.4
- }
- ],
- "GRADE_CUTOFFS" : {
- "A" : 0.87,
- "B" : 0.7,
- "C" : 0.6
- }
+ "GRADER" : [
+ {
+ "type" : "Homework",
+ "min_count" : 12,
+ "drop_count" : 2,
+ "short_label" : "HW",
+ "weight" : 0.15
+ },
+ {
+ "type" : "Lab",
+ "min_count" : 12,
+ "drop_count" : 2,
+ "category" : "Labs",
+ "weight" : 0.15
+ },
+ {
+ "type" : "Midterm",
+ "name" : "Midterm Exam",
+ "short_label" : "Midterm",
+ "weight" : 0.3
+ },
+ {
+ "type" : "Final",
+ "name" : "Final Exam",
+ "short_label" : "Final",
+ "weight" : 0.4
+ }
+ ],
+ "GRADE_CUTOFFS" : {
+ "A" : 0.87,
+ "B" : 0.7,
+ "C" : 0.6
+ }
}
-"""
+"""
class Settings(object):
def __init__(self):
-
+
# Load the global settings as a dictionary
global_settings = json.loads(global_settings_json)
-
-
+
# Load the course settings as a dictionary
course_settings = {}
try:
# TODO: this doesn't work with multicourse
- with open( settings.DATA_DIR + "/course_settings.json") as course_settings_file:
+ with open(settings.DATA_DIR + "/course_settings.json") as course_settings_file:
course_settings_string = course_settings_file.read()
course_settings = json.loads(course_settings_string)
except IOError:
log.warning("Unable to load course settings file from " + str(settings.DATA_DIR) + "/course_settings.json")
-
-
+
# Override any global settings with the course settings
global_settings.update(course_settings)
-
+
# Now, set the properties from the course settings on ourselves
for setting in global_settings:
setting_value = global_settings[setting]
setattr(self, setting, setting_value)
-
+
# Here is where we should parse any configurations, so that we can fail early
self.GRADER = graders.grader_from_conf(self.GRADER)
diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py
index b6853ae12a..c2c391b08e 100644
--- a/lms/djangoapps/courseware/courses.py
+++ b/lms/djangoapps/courseware/courses.py
@@ -1,9 +1,18 @@
+from fs.errors import ResourceNotFoundError
from functools import wraps
+import logging
+from path import path
+from django.conf import settings
from django.http import Http404
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.exceptions import ItemNotFoundError
+from static_replace import replace_urls
+from staticfiles.storage import staticfiles_storage
+
+log = logging.getLogger(__name__)
def check_course(course_id, course_must_be_open=True, course_required=True):
@@ -11,10 +20,10 @@ def check_course(course_id, course_must_be_open=True, course_required=True):
Given a course_id, this returns the course object. By default,
if the course is not found or the course is not open yet, this
method will raise a 404.
-
+
If course_must_be_open is False, the course will be returned
without a 404 even if it is not open.
-
+
If course_required is False, a course_id of None is acceptable. The
course returned will be None. Even if the course is not required,
if a course_id is given that does not exist a 404 will be raised.
@@ -24,10 +33,83 @@ def check_course(course_id, course_must_be_open=True, course_required=True):
try:
course_loc = CourseDescriptor.id_to_location(course_id)
course = modulestore().get_item(course_loc)
- except KeyError:
+ except (KeyError, ItemNotFoundError):
raise Http404("Course not found.")
-
- if course_must_be_open and not course.has_started():
+
+ started = course.has_started() or settings.MITX_FEATURES['DISABLE_START_DATES']
+ if course_must_be_open and not started:
raise Http404("This course has not yet started.")
-
+
return course
+
+
+def course_image_url(course):
+ return staticfiles_storage.url(course.metadata['data_dir'] + "/images/course_image.jpg")
+
+
+def get_course_about_section(course, section_key):
+ """
+ This returns the snippet of html to be rendered on the course about page, given the key for the section.
+ Valid keys:
+ - overview
+ - title
+ - university
+ - number
+ - short_description
+ - description
+ - key_dates (includes start, end, exams, etc)
+ - video
+ - course_staff_short
+ - course_staff_extended
+ - requirements
+ - syllabus
+ - textbook
+ - faq
+ - more_info
+ """
+
+ # Many of these are stored as html files instead of some semantic markup. This can change without effecting
+ # this interface when we find a good format for defining so many snippets of text/html.
+
+# TODO: Remove number, instructors from this list
+ if section_key in ['short_description', 'description', 'key_dates', 'video', 'course_staff_short', 'course_staff_extended',
+ 'requirements', 'syllabus', 'textbook', 'faq', 'more_info', 'number', 'instructors', 'overview',
+ 'effort', 'end_date', 'prerequisites']:
+ try:
+ with course.system.resources_fs.open(path("about") / section_key + ".html") as htmlFile:
+ return replace_urls(htmlFile.read().decode('utf-8'), course.metadata['data_dir'])
+ except ResourceNotFoundError:
+ log.warning("Missing about section {key} in course {url}".format(key=section_key, url=course.location.url()))
+ return None
+ elif section_key == "title":
+ return course.metadata.get('display_name', course.name)
+ elif section_key == "university":
+ return course.location.org
+ elif section_key == "number":
+ return course.number
+
+ raise KeyError("Invalid about key " + str(section_key))
+
+
+def get_course_info_section(course, section_key):
+ """
+ This returns the snippet of html to be rendered on the course info page, given the key for the section.
+ Valid keys:
+ - handouts
+ - guest_handouts
+ - updates
+ - guest_updates
+ """
+
+ # Many of these are stored as html files instead of some semantic markup. This can change without effecting
+ # this interface when we find a good format for defining so many snippets of text/html.
+
+ if section_key in ['handouts', 'guest_handouts', 'updates', 'guest_updates']:
+ try:
+ with course.system.resources_fs.open(path("info") / section_key + ".html") as htmlFile:
+ return replace_urls(htmlFile.read().decode('utf-8'), course.metadata['data_dir'])
+ except ResourceNotFoundError:
+ log.exception("Missing info section {key} in course {url}".format(key=section_key, url=course.location.url()))
+ return "! Info section missing !"
+
+ raise KeyError("Invalid about key " + str(section_key))
diff --git a/lms/djangoapps/courseware/migrations/0001_initial.py b/lms/djangoapps/courseware/migrations/0001_initial.py
index 205ed97136..4c29ca17a5 100644
--- a/lms/djangoapps/courseware/migrations/0001_initial.py
+++ b/lms/djangoapps/courseware/migrations/0001_initial.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Adding model 'StudentModule'
db.create_table('courseware_studentmodule', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
@@ -24,16 +25,14 @@ class Migration(SchemaMigration):
# Adding unique constraint on 'StudentModule', fields ['student', 'module_id', 'module_type']
db.create_unique('courseware_studentmodule', ['student_id', 'module_id', 'module_type'])
-
def backwards(self, orm):
-
+
# Removing unique constraint on 'StudentModule', fields ['student', 'module_id', 'module_type']
db.delete_unique('courseware_studentmodule', ['student_id', 'module_id', 'module_type'])
# Deleting model 'StudentModule'
db.delete_table('courseware_studentmodule')
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/lms/djangoapps/courseware/migrations/0002_add_indexes.py b/lms/djangoapps/courseware/migrations/0002_add_indexes.py
index 02f28c9f78..7de33b4c52 100644
--- a/lms/djangoapps/courseware/migrations/0002_add_indexes.py
+++ b/lms/djangoapps/courseware/migrations/0002_add_indexes.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Adding index on 'StudentModule', fields ['created']
db.create_index('courseware_studentmodule', ['created'])
@@ -23,9 +24,8 @@ class Migration(SchemaMigration):
# Adding index on 'StudentModule', fields ['module_id']
db.create_index('courseware_studentmodule', ['module_id'])
-
def backwards(self, orm):
-
+
# Removing index on 'StudentModule', fields ['module_id']
db.delete_index('courseware_studentmodule', ['module_id'])
@@ -41,7 +41,6 @@ class Migration(SchemaMigration):
# Removing index on 'StudentModule', fields ['created']
db.delete_index('courseware_studentmodule', ['created'])
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/lms/djangoapps/courseware/migrations/0003_done_grade_cache.py b/lms/djangoapps/courseware/migrations/0003_done_grade_cache.py
index f2fedcf1a8..96b320bc8f 100644
--- a/lms/djangoapps/courseware/migrations/0003_done_grade_cache.py
+++ b/lms/djangoapps/courseware/migrations/0003_done_grade_cache.py
@@ -4,10 +4,11 @@ from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
+
# Removing unique constraint on 'StudentModule', fields ['module_id', 'module_type', 'student']
db.delete_unique('courseware_studentmodule', ['module_id', 'module_type', 'student_id'])
@@ -20,9 +21,8 @@ class Migration(SchemaMigration):
# Adding unique constraint on 'StudentModule', fields ['module_id', 'student']
db.create_unique('courseware_studentmodule', ['module_id', 'student_id'])
-
def backwards(self, orm):
-
+
# Removing unique constraint on 'StudentModule', fields ['module_id', 'student']
db.delete_unique('courseware_studentmodule', ['module_id', 'student_id'])
@@ -35,7 +35,6 @@ class Migration(SchemaMigration):
# Adding unique constraint on 'StudentModule', fields ['module_id', 'module_type', 'student']
db.create_unique('courseware_studentmodule', ['module_id', 'module_type', 'student_id'])
-
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py
index afb7f1c494..3b0ca7fdcf 100644
--- a/lms/djangoapps/courseware/models.py
+++ b/lms/djangoapps/courseware/models.py
@@ -63,7 +63,6 @@ class StudentModule(models.Model):
# TODO (cpennington): Remove these once the LMS switches to using XModuleDescriptors
-
class StudentModuleCache(object):
"""
A cache of StudentModules for a specific student
@@ -84,7 +83,7 @@ class StudentModuleCache(object):
# that can be put into a single query
self.cache = []
chunk_size = 500
- for id_chunk in [module_ids[i:i+chunk_size] for i in xrange(0, len(module_ids), chunk_size)]:
+ for id_chunk in [module_ids[i:i + chunk_size] for i in xrange(0, len(module_ids), chunk_size)]:
self.cache.extend(StudentModule.objects.filter(
student=user,
module_state_key__in=id_chunk)
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 00ffb8608b..3f111c2953 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -5,82 +5,23 @@ from django.conf import settings
from django.http import Http404
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
-from functools import wraps
from django.contrib.auth.models import User
from xmodule.modulestore.django import modulestore
from mitxmako.shortcuts import render_to_string
from models import StudentModule, StudentModuleCache
from static_replace import replace_urls
+from xmodule.exceptions import NotFoundError
+from xmodule.x_module import ModuleSystem
+from xmodule_modifiers import replace_static_urls, add_histogram, wrap_xmodule
log = logging.getLogger("mitx.courseware")
-class I4xSystem(object):
- '''
- This is an abstraction such that x_modules can function independent
- of the courseware (e.g. import into other types of courseware, LMS,
- or if we want to have a sandbox server for user-contributed content)
-
- I4xSystem objects are passed to x_modules to provide access to system
- functionality.
-
- Note that these functions can be closures over e.g. a django request
- and user, or other environment-specific info.
- '''
- def __init__(self, ajax_url, track_function,
- get_module, render_template, replace_urls,
- user=None, filestore=None, xqueue_callback_url=None):
- '''
- Create a closure around the system environment.
-
- ajax_url - the url where ajax calls to the encapsulating module go.
- xqueue_callback_url - the url where external queueing system (e.g. for grading)
- returns its response
- track_function - function of (event_type, event), intended for logging
- or otherwise tracking the event.
- TODO: Not used, and has inconsistent args in different
- files. Update or remove.
- get_module - function that takes (location) and returns a corresponding
- module instance object.
- render_template - a function that takes (template_file, context), and returns
- rendered html.
- user - The user to base the random number generator seed off of for this request
- filestore - A filestore ojbect. Defaults to an instance of OSFS based at
- settings.DATA_DIR.
- replace_urls - TEMPORARY - A function like static_replace.replace_urls
- that capa_module can use to fix up the static urls in ajax results.
- '''
- self.ajax_url = ajax_url
- self.xqueue_callback_url = xqueue_callback_url
- self.track_function = track_function
- self.filestore = filestore
- self.get_module = get_module
- self.render_template = render_template
- self.exception404 = Http404
- self.DEBUG = settings.DEBUG
- self.seed = user.id if user is not None else 0
- self.replace_urls = replace_urls
-
- def get(self, attr):
- ''' provide uniform access to attributes (like etree).'''
- return self.__dict__.get(attr)
-
- def set(self, attr, val):
- '''provide uniform access to attributes (like etree)'''
- self.__dict__[attr] = val
-
- def __repr__(self):
- return repr(self.__dict__)
-
- def __str__(self):
- return str(self.__dict__)
-
-
def make_track_function(request):
'''
Make a tracking function that logs what happened.
- For use in I4xSystem.
+ For use in ModuleSystem.
'''
import track.views
@@ -89,28 +30,6 @@ def make_track_function(request):
return f
-def grade_histogram(module_id):
- ''' Print out a histogram of grades on a given problem.
- Part of staff member debug info.
- '''
- from django.db import connection
- cursor = connection.cursor()
-
- q = """SELECT courseware_studentmodule.grade,
- COUNT(courseware_studentmodule.student_id)
- FROM courseware_studentmodule
- WHERE courseware_studentmodule.module_id=%s
- GROUP BY courseware_studentmodule.grade"""
- # Passing module_id this way prevents sql-injection.
- cursor.execute(q, [module_id])
-
- grades = list(cursor.fetchall())
- grades.sort(key=lambda x: x[0]) # Add ORDER BY to sql query?
- if len(grades) == 1 and grades[0][0] is None:
- return []
- return grades
-
-
def toc_for_course(user, request, course, active_chapter, active_section):
'''
Create a table of contents from the module store
@@ -195,12 +114,16 @@ def get_module(user, request, location, student_module_cache, position=None):
Returns:
- a tuple (xmodule instance, instance_module, shared_module, module category).
- instance_module is a StudentModule specific to this module for this student
- shared_module is a StudentModule specific to all modules with the same 'shared_state_key' attribute, or None if the module doesn't elect to share state
+ instance_module is a StudentModule specific to this module for this student,
+ or None if this is an anonymous user
+ shared_module is a StudentModule specific to all modules with the same
+ 'shared_state_key' attribute, or None if the module doesn't elect to
+ share state
'''
descriptor = modulestore().get_item(location)
instance_module = student_module_cache.lookup(descriptor.category, descriptor.location.url())
+
shared_state_key = getattr(descriptor, 'shared_state_key', None)
if shared_state_key is not None:
shared_module = student_module_cache.lookup(descriptor.category, shared_state_key)
@@ -210,6 +133,7 @@ def get_module(user, request, location, student_module_cache, position=None):
instance_state = instance_module.state if instance_module is not None else None
shared_state = shared_module.state if shared_module is not None else None
+ # TODO (vshnayder): fix hardcoded urls (use reverse)
# Setup system context for module instance
ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.location.url() + '/'
xqueue_callback_url = settings.MITX_ROOT_URL + '/xqueue/' + str(user.id) + '/' + descriptor.location.url() + '/'
@@ -221,29 +145,31 @@ def get_module(user, request, location, student_module_cache, position=None):
# TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory
# that the xml was loaded from
- system = I4xSystem(track_function=make_track_function(request),
- render_template=render_to_string,
- ajax_url=ajax_url,
- xqueue_callback_url=xqueue_callback_url,
- # TODO (cpennington): Figure out how to share info between systems
- filestore=descriptor.system.resources_fs,
- get_module=_get_module,
- user=user,
- # TODO (cpennington): This should be removed when all html from
- # a module is coming through get_html and is therefore covered
- # by the replace_static_urls code below
- replace_urls=replace_urls,
- )
- # pass position specified in URL to module through I4xSystem
+ system = ModuleSystem(track_function=make_track_function(request),
+ render_template=render_to_string,
+ ajax_url=ajax_url,
+ xqueue_callback_url=xqueue_callback_url,
+ # TODO (cpennington): Figure out how to share info between systems
+ filestore=descriptor.system.resources_fs,
+ get_module=_get_module,
+ user=user,
+ # TODO (cpennington): This should be removed when all html from
+ # a module is coming through get_html and is therefore covered
+ # by the replace_static_urls code below
+ replace_urls=replace_urls,
+ )
+ # pass position specified in URL to module through ModuleSystem
system.set('position', position)
module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
- replace_prefix = module.metadata['data_dir']
- module = replace_static_urls(module, replace_prefix)
+ module.get_html = replace_static_urls(
+ wrap_xmodule(module.get_html, module, 'xmodule_display.html'),
+ module.metadata['data_dir']
+ )
if settings.MITX_FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF') and user.is_staff:
- module = add_histogram(module)
+ module.get_html = add_histogram(module.get_html)
# If StudentModule for this instance wasn't already in the database,
# and this isn't a guest user, create it.
@@ -271,70 +197,13 @@ def get_module(user, request, location, student_module_cache, position=None):
return (module, instance_module, shared_module, descriptor.category)
-def replace_static_urls(module, prefix):
- """
- Updates the supplied module with a new get_html function that wraps
- the old get_html function and substitutes urls of the form /static/...
- with urls that are /static//...
- """
- original_get_html = module.get_html
-
- @wraps(original_get_html)
- def get_html():
- return replace_urls(original_get_html(), staticfiles_prefix=prefix)
-
- module.get_html = get_html
- return module
-
-
-def add_histogram(module):
- """
- Updates the supplied module with a new get_html function that wraps
- the output of the old get_html function with additional information
- for admin users only, including a histogram of student answers and the
- definition of the xmodule
- """
- original_get_html = module.get_html
-
- @wraps(original_get_html)
- def get_html():
- module_id = module.id
- histogram = grade_histogram(module_id)
- render_histogram = len(histogram) > 0
-
- # TODO: fixme - no filename in module.xml in general (this code block for edx4edx)
- # the following if block is for summer 2012 edX course development; it will change when the CMS comes online
- if settings.MITX_FEATURES.get('DISPLAY_EDIT_LINK') and settings.DEBUG and module_xml.get('filename') is not None:
- coursename = multicourse_settings.get_coursename_from_request(request)
- github_url = multicourse_settings.get_course_github_url(coursename)
- fn = module_xml.get('filename')
- if module_xml.tag=='problem': fn = 'problems/' + fn # grrr
- edit_link = (github_url + '/tree/master/' + fn) if github_url is not None else None
- if module_xml.tag=='problem': edit_link += '.xml' # grrr
- else:
- edit_link = False
-
- # Cast module.definition and module.metadata to dicts so that json can dump them
- # even though they are lazily loaded
- staff_context = {'definition': json.dumps(dict(module.definition), indent=4),
- 'metadata': json.dumps(dict(module.metadata), indent=4),
- 'element_id': module.location.html_id(),
- 'edit_link': edit_link,
- 'histogram': json.dumps(histogram),
- 'render_histogram': render_histogram,
- 'module_content': original_get_html()}
- return render_to_string("staff_problem_info.html", staff_context)
-
- module.get_html = get_html
- return module
-
# TODO: TEMPORARY BYPASS OF AUTH!
@csrf_exempt
def xqueue_callback(request, userid, id, dispatch):
# Parse xqueue response
get = request.POST.copy()
try:
- header = json.loads(get.pop('xqueue_header')[0]) # 'dict'
+ header = json.loads(get.pop('xqueue_header')[0]) # 'dict'
except Exception as err:
msg = "Error in xqueue_callback %s: Invalid return format" % err
raise Exception(msg)
@@ -344,12 +213,12 @@ def xqueue_callback(request, userid, id, dispatch):
student_module_cache = StudentModuleCache(user, modulestore().get_item(id))
instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache)
-
+
if instance_module is None:
log.debug("Couldn't find module '%s' for user '%s'",
id, request.user)
raise Http404
-
+
oldgrade = instance_module.grade
old_instance_state = instance_module.state
@@ -360,7 +229,7 @@ def xqueue_callback(request, userid, id, dispatch):
# We go through the "AJAX" path
# So far, the only dispatch from xqueue will be 'score_update'
try:
- ajax_return = instance.handle_ajax(dispatch, get) # Can ignore the "ajax" return in 'xqueue_callback'
+ ajax_return = instance.handle_ajax(dispatch, get) # Can ignore the "ajax" return in 'xqueue_callback'
except:
log.exception("error processing ajax call")
raise
@@ -374,6 +243,7 @@ def xqueue_callback(request, userid, id, dispatch):
return HttpResponse("")
+
def modx_dispatch(request, dispatch=None, id=None):
''' Generic view for extensions. This is where AJAX calls go.
@@ -387,34 +257,33 @@ def modx_dispatch(request, dispatch=None, id=None):
'''
# ''' (fix emacs broken parsing)
- # If there are arguments, get rid of them
- dispatch, _, _ = dispatch.partition('?')
-
student_module_cache = StudentModuleCache(request.user, modulestore().get_item(id))
instance, instance_module, shared_module, module_type = get_module(request.user, request, id, student_module_cache)
-
- if instance_module is None:
- log.debug("Couldn't find module '%s' for user '%s'",
- id, request.user)
- raise Http404
- oldgrade = instance_module.grade
- old_instance_state = instance_module.state
- old_shared_state = shared_module.state if shared_module is not None else None
+ # Don't track state for anonymous users (who don't have student modules)
+ if instance_module is not None:
+ oldgrade = instance_module.grade
+ old_instance_state = instance_module.state
+ old_shared_state = shared_module.state if shared_module is not None else None
# Let the module handle the AJAX
try:
ajax_return = instance.handle_ajax(dispatch, request.POST)
+ except NotFoundError:
+ log.exception("Module indicating to user that request doesn't exist")
+ raise Http404
except:
log.exception("error processing ajax call")
raise
# Save the state back to the database
- instance_module.state = instance.get_instance_state()
- if instance.get_score():
- instance_module.grade = instance.get_score()['score']
- if instance_module.grade != oldgrade or instance_module.state != old_instance_state:
- instance_module.save()
+ # Don't track state for anonymous users (who don't have student modules)
+ if instance_module is not None:
+ instance_module.state = instance.get_instance_state()
+ if instance.get_score():
+ instance_module.grade = instance.get_score()['score']
+ if instance_module.grade != oldgrade or instance_module.state != old_instance_state:
+ instance_module.save()
if shared_module is not None:
shared_module.state = instance.get_shared_state()
diff --git a/lms/djangoapps/courseware/progress.py b/lms/djangoapps/courseware/progress.py
index ad2cb725b4..b9dd3fa781 100644
--- a/lms/djangoapps/courseware/progress.py
+++ b/lms/djangoapps/courseware/progress.py
@@ -1,12 +1,12 @@
class completion(object):
def __init__(self, **d):
- self.dict = dict({'duration_total':0,
- 'duration_watched':0,
- 'done':True,
- 'questions_correct':0,
- 'questions_incorrect':0,
- 'questions_total':0})
- if d:
+ self.dict = dict({'duration_total': 0,
+ 'duration_watched': 0,
+ 'done': True,
+ 'questions_correct': 0,
+ 'questions_incorrect': 0,
+ 'questions_total': 0})
+ if d:
self.dict.update(d)
def __getitem__(self, key):
@@ -23,7 +23,7 @@ class completion(object):
'questions_correct',
'questions_incorrect',
'questions_total']:
- result[item] = result[item]+other.dict[item]
+ result[item] = result[item] + other.dict[item]
return completion(**result)
def __contains__(self, key):
@@ -33,6 +33,6 @@ class completion(object):
return repr(self.dict)
if __name__ == '__main__':
- dict1=completion(duration_total=5)
- dict2=completion(duration_total=7)
- print dict1+dict2
+ dict1 = completion(duration_total=5)
+ dict2 = completion(duration_total=7)
+ print dict1 + dict2
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index c8391e0483..f273778a3c 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -1,11 +1,15 @@
+from collections import defaultdict
+import json
import logging
import urllib
+import itertools
from django.conf import settings
from django.core.context_processors import csrf
+from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
-from django.http import Http404
+from django.http import Http404, HttpResponse
from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_response, render_to_string
#from django.views.decorators.csrf import ensure_csrf_cookie
@@ -18,7 +22,7 @@ from student.models import UserProfile
from multicourse import multicourse_settings
from util.cache import cache, cache_if_anonymous
-from student.models import UserTestGroup
+from student.models import UserTestGroup, CourseEnrollment
from courseware import grades
from courseware.courses import check_course
from xmodule.modulestore.django import modulestore
@@ -27,6 +31,7 @@ log = logging.getLogger("mitx.courseware")
template_imports = {'urllib': urllib}
+
def user_groups(user):
if not user.is_authenticated():
return []
@@ -53,16 +58,20 @@ def format_url_params(params):
@cache_if_anonymous
def courses(request):
# TODO: Clean up how 'error' is done.
- context = {'courses': modulestore().get_courses()}
- return render_to_response("courses.html", context)
+ courses = sorted(modulestore().get_courses(), key=lambda course: course.number)
+ universities = defaultdict(list)
+ for course in courses:
+ universities[course.org].append(course)
+
+ return render_to_response("courses.html", {'universities': universities})
+
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def gradebook(request, course_id):
if 'course_admin' not in user_groups(request.user):
raise Http404
course = check_course(course_id)
-
-
+
student_objects = User.objects.all()[:100]
student_info = []
@@ -160,7 +169,7 @@ def index(request, course_id, chapter=None, section=None,
- HTTPresponse
'''
course = check_course(course_id)
-
+
def clean(s):
''' Fixes URLs -- we convert spaces to _ in URLs to prevent
funny encoding characters and keep the URLs readable. This undoes
@@ -249,3 +258,32 @@ def course_info(request, course_id):
course = check_course(course_id)
return render_to_response('info.html', {'course': course})
+
+
+@ensure_csrf_cookie
+@cache_if_anonymous
+def course_about(request, course_id):
+ def registered_for_course(course, user):
+ if user.is_authenticated():
+ return CourseEnrollment.objects.filter(user=user, course_id=course.id).exists()
+ else:
+ return False
+ course = check_course(course_id, course_must_be_open=False)
+ registered = registered_for_course(course, request.user)
+ return render_to_response('portal/course_about.html', {'course': course, 'registered': registered})
+
+
+@ensure_csrf_cookie
+@cache_if_anonymous
+def university_profile(request, org_id):
+ all_courses = sorted(modulestore().get_courses(), key=lambda course: course.number)
+ valid_org_ids = set(c.org for c in all_courses)
+ if org_id not in valid_org_ids:
+ raise Http404("University Profile not found for {0}".format(org_id))
+
+ # Only grab courses for this org...
+ courses = [c for c in all_courses if c.org == org_id]
+ context = dict(courses=courses, org_id=org_id)
+ template_file = "university_profile/{0}.html".format(org_id).lower()
+
+ return render_to_response(template_file, context)
diff --git a/lms/djangoapps/heartbeat/views.py b/lms/djangoapps/heartbeat/views.py
index 879c9e6016..1d053d3da0 100644
--- a/lms/djangoapps/heartbeat/views.py
+++ b/lms/djangoapps/heartbeat/views.py
@@ -2,6 +2,7 @@ import json
from datetime import datetime
from django.http import HttpResponse
+
def heartbeat(request):
"""
Simple view that a loadbalancer can check to verify that the app is up
diff --git a/lms/djangoapps/multicourse/multicourse_settings.py b/lms/djangoapps/multicourse/multicourse_settings.py
index 3c1710c535..894bfa588c 100644
--- a/lms/djangoapps/multicourse/multicourse_settings.py
+++ b/lms/djangoapps/multicourse/multicourse_settings.py
@@ -25,18 +25,18 @@ from django.conf import settings
#-----------------------------------------------------------------------------
# load course settings
-if hasattr(settings,'COURSE_SETTINGS'): # in the future, this could be replaced by reading an XML file
+if hasattr(settings, 'COURSE_SETTINGS'): # in the future, this could be replaced by reading an XML file
COURSE_SETTINGS = settings.COURSE_SETTINGS
-elif hasattr(settings,'COURSE_NAME'): # backward compatibility
+elif hasattr(settings, 'COURSE_NAME'): # backward compatibility
COURSE_SETTINGS = {settings.COURSE_NAME: {'number': settings.COURSE_NUMBER,
- 'title': settings.COURSE_TITLE,
+ 'title': settings.COURSE_TITLE,
'location': settings.COURSE_LOCATION,
},
}
else: # default to 6.002_Spring_2012
COURSE_SETTINGS = {'6.002_Spring_2012': {'number': '6.002x',
- 'title': 'Circuits and Electronics',
+ 'title': 'Circuits and Electronics',
'location': 'i4x://edx/6002xs12/course/6.002 Spring 2012',
},
}
@@ -44,6 +44,7 @@ else: # default to 6.002_Spring_2012
#-----------------------------------------------------------------------------
# wrapper functions around course settings
+
def get_coursename_from_request(request):
if 'coursename' in request.session:
coursename = request.session['coursename']
@@ -51,6 +52,7 @@ def get_coursename_from_request(request):
else: coursename = None
return coursename
+
def get_course_settings(coursename):
if not coursename:
if hasattr(settings, 'COURSE_DEFAULT'):
@@ -94,14 +96,18 @@ def get_course_title(coursename):
def get_course_number(coursename):
return get_course_property(coursename, 'number')
+
def get_course_github_url(coursename):
- return get_course_property(coursename,'github_url')
+ return get_course_property(coursename, 'github_url')
+
def get_course_default_chapter(coursename):
- return get_course_property(coursename,'default_chapter')
+ return get_course_property(coursename, 'default_chapter')
+
def get_course_default_section(coursename):
- return get_course_property(coursename,'default_section')
-
+ return get_course_property(coursename, 'default_section')
+
+
def get_course_location(coursename):
return get_course_property(coursename, 'location')
diff --git a/lms/djangoapps/multicourse/views.py b/lms/djangoapps/multicourse/views.py
index 9d081cf5cf..da9ccb77a6 100644
--- a/lms/djangoapps/multicourse/views.py
+++ b/lms/djangoapps/multicourse/views.py
@@ -3,12 +3,12 @@ from mitxmako.shortcuts import render_to_response
from multicourse import multicourse_settings
+
def mitxhome(request):
''' Home page (link from main header). List of courses. '''
if settings.DEBUG:
print "[djangoapps.multicourse.mitxhome] MITX_ROOT_URL = " + settings.MITX_ROOT_URL
if settings.ENABLE_MULTICOURSE:
- context = {'courseinfo' : multicourse_settings.COURSE_SETTINGS}
+ context = {'courseinfo': multicourse_settings.COURSE_SETTINGS}
return render_to_response("mitxhome.html", context)
return info(request)
-
diff --git a/lms/djangoapps/simplewiki/__init__.py b/lms/djangoapps/simplewiki/__init__.py
index 8c4ebf2d51..9f9c332419 100644
--- a/lms/djangoapps/simplewiki/__init__.py
+++ b/lms/djangoapps/simplewiki/__init__.py
@@ -1,4 +1,4 @@
-# Source: django-simplewiki. GPL license.
+# Source: django-simplewiki. GPL license.
import os
import sys
diff --git a/lms/djangoapps/simplewiki/admin.py b/lms/djangoapps/simplewiki/admin.py
index 8d1094bbc0..e4cf8c2f56 100644
--- a/lms/djangoapps/simplewiki/admin.py
+++ b/lms/djangoapps/simplewiki/admin.py
@@ -1,4 +1,4 @@
-# Source: django-simplewiki. GPL license.
+# Source: django-simplewiki. GPL license.
from django import forms
from django.contrib import admin
@@ -6,17 +6,21 @@ from django.utils.translation import ugettext as _
from models import Article, Revision, Permission, ArticleAttachment
+
class RevisionInline(admin.TabularInline):
model = Revision
extra = 1
+
class RevisionAdmin(admin.ModelAdmin):
list_display = ('article', '__unicode__', 'revision_date', 'revision_user', 'revision_text')
search_fields = ('article', 'counter')
+
class AttachmentAdmin(admin.ModelAdmin):
list_display = ('article', '__unicode__', 'uploaded_on', 'uploaded_by')
+
class ArticleAdminForm(forms.ModelForm):
def clean(self):
cleaned_data = self.cleaned_data
@@ -30,16 +34,19 @@ class ArticleAdminForm(forms.ModelForm):
raise forms.ValidationError(_('Article slug and parent must be '
'unique together.'))
return cleaned_data
+
class Meta:
model = Article
+
class ArticleAdmin(admin.ModelAdmin):
list_display = ('created_by', 'slug', 'modified_on', 'namespace')
search_fields = ('slug',)
- prepopulated_fields = {'slug': ('title',) }
+ prepopulated_fields = {'slug': ('title',)}
inlines = [RevisionInline]
form = ArticleAdminForm
save_on_top = True
+
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'current_revision':
# Try to determine the id of the article being edited
@@ -53,6 +60,7 @@ class ArticleAdmin(admin.ModelAdmin):
return db_field.formfield(**kwargs)
return super(ArticleAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
+
class PermissionAdmin(admin.ModelAdmin):
search_fields = ('article', 'counter')
diff --git a/lms/djangoapps/simplewiki/mdx_circuit.py b/lms/djangoapps/simplewiki/mdx_circuit.py
index bc58ad91ed..4ec7501341 100755
--- a/lms/djangoapps/simplewiki/mdx_circuit.py
+++ b/lms/djangoapps/simplewiki/mdx_circuit.py
@@ -26,19 +26,19 @@ try:
except:
from markdown import etree
+
class CircuitExtension(markdown.Extension):
def __init__(self, configs):
- for key, value in configs :
+ for key, value in configs:
self.setConfig(key, value)
-
-
+
def extendMarkdown(self, md, md_globals):
## Because Markdown treats contigous lines as one block of text, it is hard to match
## a regex that must occupy the whole line (like the circuit regex). This is why we have
## a preprocessor that inspects the lines and replaces the matched lines with text that is
## easier to match
- md.preprocessors.add('circuit', CircuitPreprocessor(md), "_begin")
-
+ md.preprocessors.add('circuit', CircuitPreprocessor(md), "_begin")
+
pattern = CircuitLink(r'processed-schematic:(?P.*?)processed-schematic-end')
pattern.md = md
pattern.ext = self
@@ -47,16 +47,16 @@ class CircuitExtension(markdown.Extension):
class CircuitPreprocessor(markdown.preprocessors.Preprocessor):
preRegex = re.compile(r'^circuit-schematic:(?P.*)$')
-
+
def run(self, lines):
def convertLine(line):
m = self.preRegex.match(line)
if m:
- return 'processed-schematic:{0}processed-schematic-end'.format( m.group('data') )
+ return 'processed-schematic:{0}processed-schematic-end'.format(m.group('data'))
else:
return line
-
- return [ convertLine(line) for line in lines ]
+
+ return [convertLine(line) for line in lines]
class CircuitLink(markdown.inlinepatterns.Pattern):
@@ -64,9 +64,9 @@ class CircuitLink(markdown.inlinepatterns.Pattern):
data = m.group('data')
data = escape(data)
return etree.fromstring("
")
-
-
+
+
def makeExtension(configs=None):
to_return = CircuitExtension(configs=configs)
- print "circuit returning " , to_return
+ print "circuit returning ", to_return
return to_return
diff --git a/lms/djangoapps/simplewiki/mdx_image.py b/lms/djangoapps/simplewiki/mdx_image.py
index 956641baa7..af0413f841 100755
--- a/lms/djangoapps/simplewiki/mdx_image.py
+++ b/lms/djangoapps/simplewiki/mdx_image.py
@@ -28,31 +28,32 @@ except:
class ImageExtension(markdown.Extension):
def __init__(self, configs):
- for key, value in configs :
+ for key, value in configs:
self.setConfig(key, value)
-
+
def add_inline(self, md, name, klass, re):
pattern = klass(re)
pattern.md = md
pattern.ext = self
md.inlinePatterns.add(name, pattern, "([^:/?#])+://)?(?P([^/?#]*)/)?(?P[^?#]*\.(?P[^?#]{3,4}))(?:\?([^#]*))?(?:#(.*))?$')
+
class ImageLink(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
img = etree.Element('img')
- proto = m.group('proto') or "http://"
+ proto = m.group('proto') or "http://"
domain = m.group('domain')
- path = m.group('path')
- ext = m.group('ext')
-
+ path = m.group('path')
+ ext = m.group('ext')
+
# A fixer upper
if ext.lower() in settings.WIKI_IMAGE_EXTENSIONS:
if domain:
- src = proto+domain+path
+ src = proto + domain + path
elif path:
# We need a nice way to source local attachments...
src = "/wiki/media/" + path + ".upload"
@@ -60,10 +61,11 @@ class ImageLink(markdown.inlinepatterns.Pattern):
src = ''
img.set('src', src)
return img
-
-def makeExtension(configs=None) :
+
+
+def makeExtension(configs=None):
return ImageExtension(configs=configs)
if __name__ == "__main__":
import doctest
- doctest.testmod()
\ No newline at end of file
+ doctest.testmod()
diff --git a/lms/djangoapps/simplewiki/mdx_mathjax.py b/lms/djangoapps/simplewiki/mdx_mathjax.py
index e694ca861e..a9148511e3 100644
--- a/lms/djangoapps/simplewiki/mdx_mathjax.py
+++ b/lms/djangoapps/simplewiki/mdx_mathjax.py
@@ -8,6 +8,7 @@ try:
except:
from markdown import etree, AtomicString
+
class MathJaxPattern(markdown.inlinepatterns.Pattern):
def __init__(self):
@@ -18,12 +19,13 @@ class MathJaxPattern(markdown.inlinepatterns.Pattern):
el.text = AtomicString(m.group(2) + m.group(3) + m.group(2))
return el
+
class MathJaxExtension(markdown.Extension):
def extendMarkdown(self, md, md_globals):
# Needs to come before escape matching because \ is pretty important in LaTeX
md.inlinePatterns.add('mathjax', MathJaxPattern(), '[A-Za-z0-9_&=-]+)\S*')
+
class Bliptv(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
url = 'http://blip.tv/scripts/flash/showplayer.swf?file=http://blip.tv/file/get/%s' % m.group('bliptvfile')
@@ -194,6 +196,7 @@ class Bliptv(markdown.inlinepatterns.Pattern):
height = self.ext.config['bliptv_height'][0]
return flash_object(url, width, height)
+
class Dailymotion(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
url = 'http://www.dailymotion.com/swf/%s' % m.group('dailymotionid').split('/')[-1]
@@ -201,6 +204,7 @@ class Dailymotion(markdown.inlinepatterns.Pattern):
height = self.ext.config['dailymotion_height'][0]
return flash_object(url, width, height)
+
class Gametrailers(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
url = 'http://www.gametrailers.com/remote_wrap.php?mid=%s' % \
@@ -209,6 +213,7 @@ class Gametrailers(markdown.inlinepatterns.Pattern):
height = self.ext.config['gametrailers_height'][0]
return flash_object(url, width, height)
+
class Metacafe(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
url = 'http://www.metacafe.com/fplayer/%s.swf' % m.group('metacafeid')
@@ -216,6 +221,7 @@ class Metacafe(markdown.inlinepatterns.Pattern):
height = self.ext.config['metacafe_height'][0]
return flash_object(url, width, height)
+
class Veoh(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
url = 'http://www.veoh.com/videodetails2.swf?permalinkId=%s' % m.group('veohid')
@@ -223,6 +229,7 @@ class Veoh(markdown.inlinepatterns.Pattern):
height = self.ext.config['veoh_height'][0]
return flash_object(url, width, height)
+
class Vimeo(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
url = 'http://vimeo.com/moogaloop.swf?clip_id=%s&server=vimeo.com' % m.group('vimeoid')
@@ -230,6 +237,7 @@ class Vimeo(markdown.inlinepatterns.Pattern):
height = self.ext.config['vimeo_height'][0]
return flash_object(url, width, height)
+
class Yahoo(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
url = "http://d.yimg.com/static.video.yahoo.com/yep/YV_YEP.swf?ver=2.2.40"
@@ -243,6 +251,7 @@ class Yahoo(markdown.inlinepatterns.Pattern):
obj.append(param)
return obj
+
class Youtube(markdown.inlinepatterns.Pattern):
def handleMatch(self, m):
url = 'http://www.youtube.com/v/%s' % m.group('youtubeargs')
@@ -250,6 +259,7 @@ class Youtube(markdown.inlinepatterns.Pattern):
height = self.ext.config['youtube_height'][0]
return flash_object(url, width, height)
+
def flash_object(url, width, height):
obj = etree.Element('object')
obj.set('type', 'application/x-shockwave-flash')
@@ -270,7 +280,8 @@ def flash_object(url, width, height):
#obj.append(param)
return obj
-def makeExtension(configs=None) :
+
+def makeExtension(configs=None):
return VideoExtension(configs=configs)
if __name__ == "__main__":
diff --git a/lms/djangoapps/simplewiki/mdx_wikipath.py b/lms/djangoapps/simplewiki/mdx_wikipath.py
index d9381e4bee..17c2b65591 100755
--- a/lms/djangoapps/simplewiki/mdx_wikipath.py
+++ b/lms/djangoapps/simplewiki/mdx_wikipath.py
@@ -33,49 +33,49 @@ class WikiPathExtension(markdown.Extension):
def __init__(self, configs):
# set extension defaults
self.config = {
- 'default_namespace' : ['edX', 'Default namespace for when one isn\'t specified.'],
- 'html_class' : ['wikipath', 'CSS hook. Leave blank for none.']
+ 'default_namespace': ['edX', 'Default namespace for when one isn\'t specified.'],
+ 'html_class': ['wikipath', 'CSS hook. Leave blank for none.']
}
-
+
# Override defaults with user settings
- for key, value in configs :
+ for key, value in configs:
# self.config[key][0] = value
self.setConfig(key, value)
-
-
+
def extendMarkdown(self, md, md_globals):
self.md = md
-
+
# append to end of inline patterns
- WIKI_RE = r'\[(?P.+?)\]\(wiki:(?P[a-zA-Z\d/_-]*)\)'
+ WIKI_RE = r'\[(?P.+?)\]\(wiki:(?P[a-zA-Z\d/_-]*)\)'
wikiPathPattern = WikiPath(WIKI_RE, self.config)
wikiPathPattern.md = md
md.inlinePatterns.add('wikipath', wikiPathPattern, " 1 and fname[-1].lower() in WIKI_IMAGE_EXTENSIONS:
return True
return False
-
+
def get_thumb(self):
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE)
@@ -181,27 +182,27 @@ class ArticleAttachment(models.Model):
return self.get_thumb_impl(*WIKI_IMAGE_THUMB_SIZE_SMALL)
def mk_thumbs(self):
- self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE, **{'force':True})
- self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE_SMALL, **{'force':True})
+ self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE, **{'force': True})
+ self.mk_thumb(*WIKI_IMAGE_THUMB_SIZE_SMALL, **{'force': True})
def mk_thumb(self, width, height, force=False):
"""Requires Python Imaging Library (PIL)"""
if not self.get_size():
return False
-
+
if not self.is_image():
return False
-
+
base_path = os.path.dirname(self.file.path)
orig_name = self.filename().split('.')
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
thumb_filepath = "%s%s%s" % (base_path, os.sep, thumb_filename)
-
+
if force or not os.path.exists(thumb_filepath):
try:
import Image
- img = Image.open(self.file.path)
- img.thumbnail((width,height), Image.ANTIALIAS)
+ img = Image.open(self.file.path)
+ img.thumbnail((width, height), Image.ANTIALIAS)
img.save(thumb_filepath)
except IOError:
return False
@@ -210,52 +211,53 @@ class ArticleAttachment(models.Model):
def get_thumb_impl(self, width, height):
"""Requires Python Imaging Library (PIL)"""
-
+
if not self.get_size():
return False
-
+
if not self.is_image():
return False
-
+
self.mk_thumb(width, height)
-
+
orig_name = self.filename().split('.')
thumb_filename = "%s__thumb__%d_%d.%s" % ('.'.join(orig_name[:-1]), width, height, orig_name[-1])
- thumb_url = settings.MEDIA_URL + WIKI_ATTACHMENTS + self.article.get_url() +'/' + thumb_filename
-
+ thumb_url = settings.MEDIA_URL + WIKI_ATTACHMENTS + self.article.get_url() + '/' + thumb_filename
+
return thumb_url
def __unicode__(self):
return self.filename()
-
+
+
class Revision(models.Model):
-
+
article = models.ForeignKey(Article, verbose_name=_('Article'))
- revision_text = models.CharField(max_length=255, blank=True, null=True,
+ revision_text = models.CharField(max_length=255, blank=True, null=True,
verbose_name=_('Description of change'))
- revision_user = models.ForeignKey(User, verbose_name=_('Modified by'),
+ revision_user = models.ForeignKey(User, verbose_name=_('Modified by'),
blank=True, null=True, related_name='wiki_revision_user')
- revision_date = models.DateTimeField(auto_now_add = True, verbose_name=_('Revision date'))
+ revision_date = models.DateTimeField(auto_now_add=True, verbose_name=_('Revision date'))
contents = models.TextField(verbose_name=_('Contents (Use MarkDown format)'))
contents_parsed = models.TextField(editable=False, blank=True, null=True)
counter = models.IntegerField(verbose_name=_('Revision#'), default=1, editable=False)
previous_revision = models.ForeignKey('self', blank=True, null=True, editable=False)
-
+
# Deleted has three values. 0 is normal, non-deleted. 1 is if it was deleted by a normal user. It should
# be a NEW revision, so that it appears in the history. 2 is a special flag that can be applied or removed
# from a normal revision. It means it has been admin-deleted, and can only been seen by an admin. It doesn't
# show up in the history.
deleted = models.IntegerField(verbose_name=_('Deleted group'), default=0)
-
+
def get_user(self):
return self.revision_user if self.revision_user else _('Anonymous')
-
+
# Called after the deleted fied has been changed (between 0 and 2). This bypasses the normal checks put in
- # save that update the revision or reject the save if contents haven't changed
+ # save that update the revision or reject the save if contents haven't changed
def adminSetDeleted(self, deleted):
self.deleted = deleted
super(Revision, self).save()
-
+
def save(self, **kwargs):
# Check if contents have changed... if not, silently ignore save
if self.article and self.article.current_revision:
@@ -265,7 +267,7 @@ class Revision(models.Model):
import datetime
self.article.modified_on = datetime.datetime.now()
self.article.save()
-
+
# Increment counter according to previous revision
previous_revision = Revision.objects.filter(article=self.article).order_by('-counter')
if previous_revision.count() > 0:
@@ -280,50 +282,50 @@ class Revision(models.Model):
# Create pre-parsed contents - no need to parse on-the-fly
ext = WIKI_MARKDOWN_EXTENSIONS
- ext += ["wikipath(default_namespace=%s)" % self.article.namespace.name ]
+ ext += ["wikipath(default_namespace=%s)" % self.article.namespace.name]
self.contents_parsed = markdown(self.contents,
extensions=ext,
safe_mode='escape',)
super(Revision, self).save(**kwargs)
-
+
def delete(self, **kwargs):
"""If a current revision is deleted, then regress to the previous
revision or insert a stub, if no other revisions are available"""
article = self.article
if article.current_revision == self:
- prev_revision = Revision.objects.filter(article__exact = article,
- pk__not = self.pk).order_by('-counter')
+ prev_revision = Revision.objects.filter(article__exact=article,
+ pk__not=self.pk).order_by('-counter')
if prev_revision:
article.current_revision = prev_revision[0]
article.save()
else:
- r = Revision(article=article,
- revision_user = article.created_by)
+ r = Revision(article=article,
+ revision_user=article.created_by)
r.contents = unicode(_('Auto-generated stub'))
- r.revision_text= unicode(_('Auto-generated stub'))
+ r.revision_text = unicode(_('Auto-generated stub'))
r.save()
article.current_revision = r
article.save()
super(Revision, self).delete(**kwargs)
-
+
def get_diff(self):
if (self.deleted == 1):
yield "Article Deletion"
return
-
+
if self.previous_revision:
previous = self.previous_revision.contents.splitlines(1)
else:
previous = []
-
+
# Todo: difflib.HtmlDiff would look pretty for our history pages!
diff = difflib.unified_diff(previous, self.contents.splitlines(1))
# let's skip the preamble
diff.next(); diff.next(); diff.next()
-
+
for d in diff:
yield d
-
+
def __unicode__(self):
return "r%d" % self.counter
@@ -331,33 +333,45 @@ class Revision(models.Model):
verbose_name = _('article revision')
verbose_name_plural = _('article revisions')
+
class Permission(models.Model):
- permission_name = models.CharField(max_length = 255, verbose_name=_('Permission name'))
+ permission_name = models.CharField(max_length=255, verbose_name=_('Permission name'))
can_write = models.ManyToManyField(User, blank=True, null=True, related_name='write',
help_text=_('Select none to grant anonymous access.'))
can_read = models.ManyToManyField(User, blank=True, null=True, related_name='read',
help_text=_('Select none to grant anonymous access.'))
+
def __unicode__(self):
return self.permission_name
+
class Meta:
verbose_name = _('Article permission')
verbose_name_plural = _('Article permissions')
+
class RevisionForm(forms.ModelForm):
- contents = forms.CharField(label=_('Contents'), widget=forms.Textarea(attrs={'rows':8, 'cols':50}))
+ contents = forms.CharField(label=_('Contents'), widget=forms.Textarea(attrs={'rows': 8, 'cols': 50}))
+
class Meta:
model = Revision
fields = ['contents', 'revision_text']
+
+
class RevisionFormWithTitle(forms.ModelForm):
title = forms.CharField(label=_('Title'))
+
class Meta:
model = Revision
fields = ['title', 'contents', 'revision_text']
+
+
class CreateArticleForm(RevisionForm):
title = forms.CharField(label=_('Title'))
+
class Meta:
model = Revision
- fields = ['title', 'contents',]
+ fields = ['title', 'contents', ]
+
def set_revision(sender, *args, **kwargs):
"""Signal handler to ensure that a new revision is always chosen as the
diff --git a/lms/djangoapps/simplewiki/templatetags/simplewiki_utils.py b/lms/djangoapps/simplewiki/templatetags/simplewiki_utils.py
index 18c6332b1a..6325aeb2bd 100644
--- a/lms/djangoapps/simplewiki/templatetags/simplewiki_utils.py
+++ b/lms/djangoapps/simplewiki/templatetags/simplewiki_utils.py
@@ -7,11 +7,13 @@ from simplewiki.wiki_settings import *
register = template.Library()
+
@register.filter()
def prepend_media_url(value):
"""Prepend user defined media root to url"""
return settings.MEDIA_URL + value
+
@register.filter()
def urlquote(value):
"""Prepend user defined media root to url"""
diff --git a/lms/djangoapps/simplewiki/tests.py b/lms/djangoapps/simplewiki/tests.py
index 2247054b35..6b60485805 100644
--- a/lms/djangoapps/simplewiki/tests.py
+++ b/lms/djangoapps/simplewiki/tests.py
@@ -7,6 +7,7 @@ Replace these with more appropriate tests for your application.
from django.test import TestCase
+
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
@@ -20,4 +21,3 @@ Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}
-
diff --git a/lms/djangoapps/simplewiki/urls.py b/lms/djangoapps/simplewiki/urls.py
index 2d01c06bf9..6179345f9a 100644
--- a/lms/djangoapps/simplewiki/urls.py
+++ b/lms/djangoapps/simplewiki/urls.py
@@ -14,6 +14,6 @@ urlpatterns = patterns('',
url(r'^search_related' + article_slug, 'simplewiki.views.search_add_related', name='search_related'),
url(r'^random/?$', 'simplewiki.views.random_article', name='wiki_random'),
url(r'^revision_feed' + namespace + r'/(?P[0-9]+)?$', 'simplewiki.views.revision_feed', name='wiki_revision_feed'),
- url(r'^search' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
- url(r'^list' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), #Just an alias for the search, but you usually don't submit a search term
+ url(r'^search' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_search_articles'),
+ url(r'^list' + namespace + r'?$', 'simplewiki.views.search_articles', name='wiki_list_articles'), # Just an alias for the search, but you usually don't submit a search term
)
diff --git a/lms/djangoapps/simplewiki/views.py b/lms/djangoapps/simplewiki/views.py
index a0f1463fe5..77fadc49ba 100644
--- a/lms/djangoapps/simplewiki/views.py
+++ b/lms/djangoapps/simplewiki/views.py
@@ -15,9 +15,10 @@ from xmodule.modulestore.django import modulestore
from models import Revision, Article, Namespace, CreateArticleForm, RevisionFormWithTitle, RevisionForm
import wiki_settings
-
-def wiki_reverse(wiki_page, article = None, course = None, namespace=None, args=[], kwargs={}):
- kwargs = dict(kwargs) # TODO: Figure out why if I don't do this kwargs sometimes contains {'article_path'}
+
+
+def wiki_reverse(wiki_page, article=None, course=None, namespace=None, args=[], kwargs={}):
+ kwargs = dict(kwargs) # TODO: Figure out why if I don't do this kwargs sometimes contains {'article_path'}
if not 'course_id' in kwargs and course:
kwargs['course_id'] = course.id
if not 'article_path' in kwargs and article:
@@ -25,86 +26,91 @@ def wiki_reverse(wiki_page, article = None, course = None, namespace=None, args=
if not 'namespace' in kwargs and namespace:
kwargs['namespace'] = namespace
return reverse(wiki_page, kwargs=kwargs, args=args)
-
-def update_template_dictionary(dictionary, request = None, course = None, article = None, revision = None):
+
+
+def update_template_dictionary(dictionary, request=None, course=None, article=None, revision=None):
if article:
dictionary['wiki_article'] = article
- dictionary['wiki_title'] = article.title #TODO: What is the title when viewing the article in a course?
+ dictionary['wiki_title'] = article.title # TODO: What is the title when viewing the article in a course?
if not course and 'namespace' not in dictionary:
dictionary['namespace'] = article.namespace.name
-
+
if course:
dictionary['course'] = course
if 'namespace' not in dictionary:
dictionary['namespace'] = course.wiki_namespace
else:
dictionary['course'] = None
-
+
if revision:
dictionary['wiki_article_revision'] = revision
dictionary['wiki_current_revision_deleted'] = not (revision.deleted == 0)
-
+
if request:
dictionary.update(csrf(request))
-
+
+
def view(request, article_path, course_id=None):
course = check_course(course_id, course_required=False)
-
- (article, err) = get_article(request, article_path, course )
+
+ (article, err) = get_article(request, article_path, course)
if err:
return err
-
+
perm_err = check_permissions(request, article, course, check_read=True, check_deleted=True)
if perm_err:
return perm_err
-
+
d = {}
update_template_dictionary(d, request, course, article, article.current_revision)
return render_to_response('simplewiki/simplewiki_view.html', d)
-
+
+
def view_revision(request, revision_number, article_path, course_id=None):
course = check_course(course_id, course_required=False)
-
- (article, err) = get_article(request, article_path, course )
+
+ (article, err) = get_article(request, article_path, course)
if err:
return err
-
+
try:
revision = Revision.objects.get(counter=int(revision_number), article=article)
except:
d = {'wiki_err_norevision': revision_number}
update_template_dictionary(d, request, course, article)
return render_to_response('simplewiki/simplewiki_error.html', d)
-
+
perm_err = check_permissions(request, article, course, check_read=True, check_deleted=True, revision=revision)
if perm_err:
return perm_err
-
+
d = {}
update_template_dictionary(d, request, course, article, revision)
-
+
return render_to_response('simplewiki/simplewiki_view.html', d)
+
def root_redirect(request, course_id=None):
course = check_course(course_id, course_required=False)
-
+
#TODO: Add a default namespace to settings.
namespace = course.wiki_namespace if course else "edX"
-
+
try:
root = Article.get_root(namespace)
- return HttpResponseRedirect(reverse('wiki_view', kwargs={'course_id' : course_id, 'article_path' : root.get_path()} ))
+ return HttpResponseRedirect(reverse('wiki_view', kwargs={'course_id': course_id, 'article_path': root.get_path()}))
except:
# If the root is not found, we probably are loading this class for the first time
# We should make sure the namespace exists so the root article can be created.
Namespace.ensure_namespace(namespace)
-
+
err = not_found(request, namespace + '/', course)
return err
-def create(request, article_path, course_id=None):
+
+def create(request, article_path, course_id=None):
course = check_course(course_id, course_required=False)
-
+
article_path_components = article_path.split('/')
# Ensure the namespace exists
@@ -112,27 +118,27 @@ def create(request, article_path, course_id=None):
d = {'wiki_err_no_namespace': True}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_error.html', d)
-
+
namespace = None
try:
- namespace = Namespace.objects.get(name__exact = article_path_components[0])
+ namespace = Namespace.objects.get(name__exact=article_path_components[0])
except Namespace.DoesNotExist, ValueError:
d = {'wiki_err_bad_namespace': True}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_error.html', d)
-
+
# See if the article already exists
article_slug = article_path_components[1] if len(article_path_components) >= 2 else ''
#TODO: Make sure the slug only contains legal characters (which is already done a bit by the url regex)
-
+
try:
- existing_article = Article.objects.get(namespace = namespace, slug__exact = article_slug)
+ existing_article = Article.objects.get(namespace=namespace, slug__exact=article_slug)
#It already exists, so we just redirect to view the article
return HttpResponseRedirect(wiki_reverse("wiki_view", existing_article, course))
except Article.DoesNotExist:
#This is good. The article doesn't exist
pass
-
+
#TODO: Once we have permissions for namespaces, we should check for create permissions
#check_permissions(request, #namespace#, check_locked=False, check_write=True, check_deleted=True)
@@ -151,21 +157,22 @@ def create(request, article_path, course_id=None):
new_revision.revision_user = request.user
new_revision.article = article
new_revision.save()
-
+
return HttpResponseRedirect(wiki_reverse("wiki_view", article, course))
else:
- f = CreateArticleForm(initial={'title':request.GET.get('wiki_article_name', article_slug),
- 'contents':_('Headline\n===\n\n')})
-
- d = {'wiki_form': f, 'create_article' : True, 'namespace' : namespace.name}
+ f = CreateArticleForm(initial={'title': request.GET.get('wiki_article_name', article_slug),
+ 'contents': _('Headline\n===\n\n')})
+
+ d = {'wiki_form': f, 'create_article': True, 'namespace': namespace.name}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_edit.html', d)
+
def edit(request, article_path, course_id=None):
course = check_course(course_id, course_required=False)
-
- (article, err) = get_article(request, article_path, course )
+
+ (article, err) = get_article(request, article_path, course)
if err:
return err
@@ -178,21 +185,21 @@ def edit(request, article_path, course_id=None):
EditForm = RevisionFormWithTitle
else:
EditForm = RevisionForm
-
+
if request.method == 'POST':
f = EditForm(request.POST)
if f.is_valid():
new_revision = f.save(commit=False)
new_revision.article = article
-
+
if request.POST.__contains__('delete'):
- if (article.current_revision.deleted == 1): #This article has already been deleted. Redirect
+ if (article.current_revision.deleted == 1): # This article has already been deleted. Redirect
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
new_revision.contents = ""
new_revision.deleted = 1
elif not new_revision.get_diff():
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
-
+
if not request.user.is_anonymous():
new_revision.revision_user = request.user
new_revision.save()
@@ -202,17 +209,18 @@ def edit(request, article_path, course_id=None):
return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
else:
startContents = article.current_revision.contents if (article.current_revision.deleted == 0) else 'Headline\n===\n\n'
-
+
f = EditForm({'contents': startContents, 'title': article.title})
-
+
d = {'wiki_form': f}
update_template_dictionary(d, request, course, article)
return render_to_response('simplewiki/simplewiki_edit.html', d)
+
def history(request, article_path, page=1, course_id=None):
course = check_course(course_id, course_required=False)
-
- (article, err) = get_article(request, article_path, course )
+
+ (article, err) = get_article(request, article_path, course)
if err:
return err
@@ -221,22 +229,22 @@ def history(request, article_path, page=1, course_id=None):
return perm_err
page_size = 10
-
+
if page == None:
page = 1
try:
p = int(page)
except ValueError:
p = 1
-
- history = Revision.objects.filter(article__exact = article).order_by('-counter').select_related('previous_revision__counter', 'revision_user', 'wiki_article')
-
+
+ history = Revision.objects.filter(article__exact=article).order_by('-counter').select_related('previous_revision__counter', 'revision_user', 'wiki_article')
+
if request.method == 'POST':
- if request.POST.__contains__('revision'): #They selected a version, but they can be either deleting or changing the version
+ if request.POST.__contains__('revision'): # They selected a version, but they can be either deleting or changing the version
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True)
if perm_err:
return perm_err
-
+
redirectURL = wiki_reverse('wiki_view', article, course)
try:
r = int(request.POST['revision'])
@@ -246,7 +254,7 @@ def history(request, article_path, page=1, course_id=None):
article.save()
elif request.POST.__contains__('view'):
redirectURL = wiki_reverse('wiki_view_revision', course=course,
- kwargs={'revision_number' : revision.counter, 'article_path' : article.get_path()})
+ kwargs={'revision_number': revision.counter, 'article_path': article.get_path()})
#The rese of these are admin functions
elif request.POST.__contains__('delete') and request.user.is_superuser:
if (revision.deleted == 0):
@@ -255,7 +263,7 @@ def history(request, article_path, page=1, course_id=None):
if (revision.deleted == 2):
revision.adminSetDeleted(0)
elif request.POST.__contains__('delete_all') and request.user.is_superuser:
- Revision.objects.filter(article__exact = article, deleted = 0).update(deleted = 2)
+ Revision.objects.filter(article__exact=article, deleted=0).update(deleted=2)
elif request.POST.__contains__('lock_article'):
article.locked = not article.locked
article.save()
@@ -264,88 +272,88 @@ def history(request, article_path, page=1, course_id=None):
pass
finally:
return HttpResponseRedirect(redirectURL)
- #
- #
+ #
+ #
#
#
#
# %else:
#
- #
-
- page_count = (history.count()+(page_size-1)) / page_size
+ #
+
+ page_count = (history.count() + (page_size - 1)) / page_size
if p > page_count:
p = 1
- beginItem = (p-1) * page_size
-
+ beginItem = (p - 1) * page_size
+
next_page = p + 1 if page_count > p else None
prev_page = p - 1 if p > 1 else None
-
-
+
d = {'wiki_page': p,
- 'wiki_next_page': next_page,
- 'wiki_prev_page': prev_page,
- 'wiki_history': history[beginItem:beginItem+page_size],
- 'show_delete_revision' : request.user.is_superuser}
+ 'wiki_next_page': next_page,
+ 'wiki_prev_page': prev_page,
+ 'wiki_history': history[beginItem:beginItem + page_size],
+ 'show_delete_revision': request.user.is_superuser}
update_template_dictionary(d, request, course, article)
-
+
return render_to_response('simplewiki/simplewiki_history.html', d)
-
+
+
def revision_feed(request, page=1, namespace=None, course_id=None):
course = check_course(course_id, course_required=False)
-
+
page_size = 10
-
+
if page == None:
page = 1
try:
p = int(page)
except ValueError:
p = 1
-
+
history = Revision.objects.order_by('-revision_date').select_related('revision_user', 'article', 'previous_revision')
-
- page_count = (history.count()+(page_size-1)) / page_size
+
+ page_count = (history.count() + (page_size - 1)) / page_size
if p > page_count:
p = 1
- beginItem = (p-1) * page_size
-
+ beginItem = (p - 1) * page_size
+
next_page = p + 1 if page_count > p else None
prev_page = p - 1 if p > 1 else None
-
+
d = {'wiki_page': p,
- 'wiki_next_page': next_page,
- 'wiki_prev_page': prev_page,
- 'wiki_history': history[beginItem:beginItem+page_size],
- 'show_delete_revision' : request.user.is_superuser,
- 'namespace' : namespace}
+ 'wiki_next_page': next_page,
+ 'wiki_prev_page': prev_page,
+ 'wiki_history': history[beginItem:beginItem + page_size],
+ 'show_delete_revision': request.user.is_superuser,
+ 'namespace': namespace}
update_template_dictionary(d, request, course)
-
+
return render_to_response('simplewiki/simplewiki_revision_feed.html', d)
-def search_articles(request, namespace=None, course_id = None):
+
+def search_articles(request, namespace=None, course_id=None):
course = check_course(course_id, course_required=False)
-
+
# blampe: We should check for the presence of other popular django search
# apps and use those if possible. Only fall back on this as a last resort.
# Adding some context to results (eg where matches were) would also be nice.
-
+
# todo: maybe do some perm checking here
-
+
if request.method == 'GET':
querystring = request.GET.get('value', '').strip()
else:
querystring = ""
-
+
results = Article.objects.all()
if namespace:
- results = results.filter(namespace__name__exact = namespace)
-
+ results = results.filter(namespace__name__exact=namespace)
+
if request.user.is_superuser:
results = results.order_by('current_revision__deleted')
else:
- results = results.filter(current_revision__deleted = 0)
-
+ results = results.filter(current_revision__deleted=0)
if querystring:
for queryword in querystring.split():
@@ -355,27 +363,28 @@ def search_articles(request, namespace=None, course_id = None):
queryword = queryword[1:]
else:
results._search = lambda x: results.filter(x)
-
- results = results._search(Q(current_revision__contents__icontains = queryword) | \
- Q(title__icontains = queryword))
-
- results = results.select_related('current_revision__deleted','namespace')
-
- results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_path().lower()) )
+
+ results = results._search(Q(current_revision__contents__icontains=queryword) | \
+ Q(title__icontains=queryword))
+
+ results = results.select_related('current_revision__deleted', 'namespace')
+
+ results = sorted(results, key=lambda article: (article.current_revision.deleted, article.get_path().lower()))
if len(results) == 1 and querystring:
- return HttpResponseRedirect(wiki_reverse('wiki_view', article=results[0], course=course ))
+ return HttpResponseRedirect(wiki_reverse('wiki_view', article=results[0], course=course))
else:
d = {'wiki_search_results': results,
- 'wiki_search_query': querystring,
- 'namespace' : namespace}
+ 'wiki_search_query': querystring,
+ 'namespace': namespace}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_searchresults.html', d)
-
+
+
def search_add_related(request, course_id, slug, namespace):
course = check_course(course_id, course_required=False)
-
- (article, err) = get_article(request, slug, namespace if namespace else course_id )
+
+ (article, err) = get_article(request, slug, namespace if namespace else course_id)
if err:
return err
@@ -387,12 +396,12 @@ def search_add_related(request, course_id, slug, namespace):
self_pk = request.GET.get('self', None)
if search_string:
results = []
- related = Article.objects.filter(title__istartswith = search_string)
+ related = Article.objects.filter(title__istartswith=search_string)
others = article.related.all()
if self_pk:
related = related.exclude(pk=self_pk)
if others:
- related = related.exclude(related__in = others)
+ related = related.exclude(related__in=others)
related = related.order_by('title')[:10]
for item in related:
results.append({'id': str(item.id),
@@ -400,21 +409,22 @@ def search_add_related(request, course_id, slug, namespace):
'info': item.get_url()})
else:
results = []
-
+
json = simplejson.dumps({'results': results})
return HttpResponse(json, mimetype='application/json')
+
def add_related(request, course_id, slug, namespace):
course = check_course(course_id, course_required=False)
-
- (article, err) = get_article(request, slug, namespace if namespace else course_id )
+
+ (article, err) = get_article(request, slug, namespace if namespace else course_id)
if err:
return err
-
+
perm_err = check_permissions(request, article, course, check_write=True, check_locked=True)
if perm_err:
return perm_err
-
+
try:
related_id = request.POST['id']
rel = Article.objects.get(id=related_id)
@@ -427,11 +437,12 @@ def add_related(request, course_id, slug, namespace):
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
+
def remove_related(request, course_id, namespace, slug, related_id):
course = check_course(course_id, course_required=False)
-
- (article, err) = get_article(request, slug, namespace if namespace else course_id )
-
+
+ (article, err) = get_article(request, slug, namespace if namespace else course_id)
+
if err:
return err
@@ -449,53 +460,57 @@ def remove_related(request, course_id, namespace, slug, related_id):
finally:
return HttpResponseRedirect(reverse('wiki_view', args=(article.get_url(),)))
+
def random_article(request, course_id=None):
course = check_course(course_id, course_required=False)
-
+
from random import randint
num_arts = Article.objects.count()
- article = Article.objects.all()[randint(0, num_arts-1)]
- return HttpResponseRedirect( wiki_reverse('wiki_view', article, course))
-
+ article = Article.objects.all()[randint(0, num_arts - 1)]
+ return HttpResponseRedirect(wiki_reverse('wiki_view', article, course))
+
+
def not_found(request, article_path, course):
"""Generate a NOT FOUND message for some URL"""
d = {'wiki_err_notfound': True,
'article_path': article_path,
- 'namespace' : course.wiki_namespace}
+ 'namespace': course.wiki_namespace}
update_template_dictionary(d, request, course)
return render_to_response('simplewiki/simplewiki_error.html', d)
-
+
+
def get_article(request, article_path, course):
err = None
article = None
-
+
try:
article = Article.get_article(article_path)
except Article.DoesNotExist, ValueError:
err = not_found(request, article_path, course)
-
+
return (article, err)
-def check_permissions(request, article, course, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision = None):
+
+def check_permissions(request, article, course, check_read=False, check_write=False, check_locked=False, check_deleted=False, revision=None):
read_err = check_read and not article.can_read(request.user)
-
+
write_err = check_write and not article.can_write(request.user)
-
+
locked_err = check_locked and article.locked
-
+
if revision is None:
revision = article.current_revision
deleted_err = check_deleted and not (revision.deleted == 0)
if (request.user.is_superuser):
deleted_err = False
locked_err = False
-
+
if read_err or write_err or locked_err or deleted_err:
d = {'wiki_article': article,
- 'wiki_err_noread': read_err,
- 'wiki_err_nowrite': write_err,
- 'wiki_err_locked': locked_err,
- 'wiki_err_deleted': deleted_err,}
+ 'wiki_err_noread': read_err,
+ 'wiki_err_nowrite': write_err,
+ 'wiki_err_locked': locked_err,
+ 'wiki_err_deleted': deleted_err, }
update_template_dictionary(d, request, course)
# TODO: Make this a little less jarring by just displaying an error
# on the current page? (no such redirect happens for an anon upload yet)
@@ -511,21 +526,21 @@ def check_permissions(request, article, course, check_read=False, check_write=Fa
if wiki_settings.WIKI_REQUIRE_LOGIN_VIEW:
- view = login_required(view)
- history = login_required(history)
- search_articles = login_required(search_articles)
- root_redirect = login_required(root_redirect)
- revision_feed = login_required(revision_feed)
- random_article = login_required(random_article)
+ view = login_required(view)
+ history = login_required(history)
+ search_articles = login_required(search_articles)
+ root_redirect = login_required(root_redirect)
+ revision_feed = login_required(revision_feed)
+ random_article = login_required(random_article)
search_add_related = login_required(search_add_related)
- not_found = login_required(not_found)
- view_revision = login_required(view_revision)
-
+ not_found = login_required(not_found)
+ view_revision = login_required(view_revision)
+
if wiki_settings.WIKI_REQUIRE_LOGIN_EDIT:
- create = login_required(create)
- edit = login_required(edit)
- add_related = login_required(add_related)
- remove_related = login_required(remove_related)
+ create = login_required(create)
+ edit = login_required(edit)
+ add_related = login_required(add_related)
+ remove_related = login_required(remove_related)
if wiki_settings.WIKI_CONTEXT_PREPROCESSORS:
settings.TEMPLATE_CONTEXT_PROCESSORS += wiki_settings.WIKI_CONTEXT_PREPROCESSORS
diff --git a/lms/djangoapps/simplewiki/wiki_settings.py b/lms/djangoapps/simplewiki/wiki_settings.py
index 88363901a8..6054ab1909 100644
--- a/lms/djangoapps/simplewiki/wiki_settings.py
+++ b/lms/djangoapps/simplewiki/wiki_settings.py
@@ -94,18 +94,18 @@ WIKI_MARKDOWN_EXTENSIONS = getattr(settings, 'SIMPLE_WIKI_MARKDOWN_EXTENSIONS',
])
-WIKI_IMAGE_EXTENSIONS = getattr(settings,
+WIKI_IMAGE_EXTENSIONS = getattr(settings,
'SIMPLE_WIKI_IMAGE_EXTENSIONS',
- ('jpg','jpeg','gif','png','tiff','bmp'))
+ ('jpg', 'jpeg', 'gif', 'png', 'tiff', 'bmp'))
# Planned features
-WIKI_PAGE_WIDTH = getattr(settings,
+WIKI_PAGE_WIDTH = getattr(settings,
'SIMPLE_WIKI_PAGE_WIDTH', "100%")
-
-WIKI_PAGE_ALIGN = getattr(settings,
+
+WIKI_PAGE_ALIGN = getattr(settings,
'SIMPLE_WIKI_PAGE_ALIGN', "center")
-
-WIKI_IMAGE_THUMB_SIZE = getattr(settings,
- 'SIMPLE_WIKI_IMAGE_THUMB_SIZE', (200,150))
-
-WIKI_IMAGE_THUMB_SIZE_SMALL = getattr(settings,
- 'SIMPLE_WIKI_IMAGE_THUMB_SIZE_SMALL', (100,100))
+
+WIKI_IMAGE_THUMB_SIZE = getattr(settings,
+ 'SIMPLE_WIKI_IMAGE_THUMB_SIZE', (200, 150))
+
+WIKI_IMAGE_THUMB_SIZE_SMALL = getattr(settings,
+ 'SIMPLE_WIKI_IMAGE_THUMB_SIZE_SMALL', (100, 100))
diff --git a/lms/djangoapps/ssl_auth/ssl_auth.py b/lms/djangoapps/ssl_auth/ssl_auth.py
index df3029da93..adbb2bf94d 100755
--- a/lms/djangoapps/ssl_auth/ssl_auth.py
+++ b/lms/djangoapps/ssl_auth/ssl_auth.py
@@ -8,31 +8,35 @@ from django.contrib.auth.models import User, check_password
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.middleware import RemoteUserMiddleware
from django.core.exceptions import ImproperlyConfigured
-import os, string, re
+import os
+import string
+import re
from random import choice
from student.models import UserProfile
#-----------------------------------------------------------------------------
+
def ssl_dn_extract_info(dn):
'''
Extract username, email address (may be anyuser@anydomain.com) and full name
from the SSL DN string. Return (user,email,fullname) if successful, and None
otherwise.
'''
- ss = re.search('/emailAddress=(.*)@([^/]+)',dn)
+ ss = re.search('/emailAddress=(.*)@([^/]+)', dn)
if ss:
user = ss.group(1)
- email = "%s@%s" % (user,ss.group(2))
+ email = "%s@%s" % (user, ss.group(2))
else:
return None
- ss = re.search('/CN=([^/]+)/',dn)
+ ss = re.search('/CN=([^/]+)/', dn)
if ss:
fullname = ss.group(1)
else:
return None
- return (user,email,fullname)
+ return (user, email, fullname)
+
def check_nginx_proxy(request):
'''
@@ -40,7 +44,7 @@ def check_nginx_proxy(request):
If so, get user info from the SSL DN string and return that, as (user,email,fullname)
'''
m = request.META
- if m.has_key('HTTP_X_REAL_IP'): # we're behind a nginx reverse proxy, which has already done ssl auth
+ if m.has_key('HTTP_X_REAL_IP'): # we're behind a nginx reverse proxy, which has already done ssl auth
if not m.has_key('HTTP_SSL_CLIENT_S_DN'):
return None
dn = m['HTTP_SSL_CLIENT_S_DN']
@@ -49,6 +53,7 @@ def check_nginx_proxy(request):
#-----------------------------------------------------------------------------
+
def get_ssl_username(request):
x = check_nginx_proxy(request)
if x:
@@ -62,14 +67,15 @@ def get_ssl_username(request):
#-----------------------------------------------------------------------------
+
class NginxProxyHeaderMiddleware(RemoteUserMiddleware):
'''
Django "middleware" function for extracting user information from HTTP request.
-
+
'''
# this field is generated by nginx's reverse proxy
- header = 'HTTP_SSL_CLIENT_S_DN' # specify the request.META field to use
-
+ header = 'HTTP_SSL_CLIENT_S_DN' # specify the request.META field to use
+
def process_request(self, request):
# AuthenticationMiddleware is required so that request.user exists.
if not hasattr(request, 'user'):
@@ -83,10 +89,10 @@ class NginxProxyHeaderMiddleware(RemoteUserMiddleware):
#raise ImproperlyConfigured('[ProxyHeaderMiddleware] request.META=%s' % repr(request.META))
try:
- username = request.META[self.header] # try the nginx META key first
+ username = request.META[self.header] # try the nginx META key first
except KeyError:
try:
- env = request._req.subprocess_env # else try the direct apache2 SSL key
+ env = request._req.subprocess_env # else try the direct apache2 SSL key
if env.has_key('SSL_CLIENT_S_DN'):
username = env['SSL_CLIENT_S_DN']
else:
@@ -117,19 +123,20 @@ class NginxProxyHeaderMiddleware(RemoteUserMiddleware):
request.user = user
if settings.DEBUG: print "[ssl_auth.ssl_auth.NginxProxyHeaderMiddleware] logging in user=%s" % user
auth.login(request, user)
-
- def clean_username(self,username,request):
+
+ def clean_username(self, username, request):
'''
username is the SSL DN string - extract the actual username from it and return
'''
info = ssl_dn_extract_info(username)
if not info:
return None
- (username,email,fullname) = info
+ (username, email, fullname) = info
return username
#-----------------------------------------------------------------------------
+
class SSLLoginBackend(ModelBackend):
'''
Django authentication back-end which auto-logs-in a user based on having
@@ -140,14 +147,14 @@ class SSLLoginBackend(ModelBackend):
# remote_user is from the SSL_DN string. It will be non-empty only when
# the user has already passed the server authentication, which means
# matching with the certificate authority.
- if not remote_user:
+ if not remote_user:
# no remote_user, so check username (but don't auto-create user)
if not username:
return None
- return None # pass on to another authenticator backend
+ return None # pass on to another authenticator backend
#raise ImproperlyConfigured("in SSLLoginBackend, username=%s, remote_user=%s" % (username,remote_user))
try:
- user = User.objects.get(username=username) # if user already exists don't create it
+ user = User.objects.get(username=username) # if user already exists don't create it
return user
except User.DoesNotExist:
return None
@@ -168,15 +175,15 @@ class SSLLoginBackend(ModelBackend):
if not info:
#raise ImproperlyConfigured("[SSLLoginBackend] remote_user=%s, info=%s" % (remote_user,info))
return None
- (username,email,fullname) = info
+ (username, email, fullname) = info
try:
- user = User.objects.get(username=username) # if user already exists don't create it
+ user = User.objects.get(username=username) # if user already exists don't create it
except User.DoesNotExist:
if not settings.DEBUG:
raise "User does not exist. Not creating user; potential schema consistency issues"
#raise ImproperlyConfigured("[SSLLoginBackend] creating %s" % repr(info))
- user = User(username=username, password=GenPasswd()) # create new User
+ user = User(username=username, password=GenPasswd()) # create new User
user.is_staff = False
user.is_superuser = False
# get first, last name from fullname
@@ -200,7 +207,7 @@ class SSLLoginBackend(ModelBackend):
user.last_name = user.last_name.strip()
# save
user.save()
-
+
# auto-create user profile
up = UserProfile(user=user)
up.name = fullname
@@ -223,16 +230,17 @@ class SSLLoginBackend(ModelBackend):
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
-
+
#-----------------------------------------------------------------------------
# OLD!
+
class AutoLoginBackend:
def authenticate(self, username=None, password=None):
raise ImproperlyConfigured("in AutoLoginBackend, username=%s" % username)
if not os.environ.has_key('HTTPS'):
return None
- if not os.environ.get('HTTPS')=='on':# only use this back-end if HTTPS on
+ if not os.environ.get('HTTPS') == 'on':# only use this back-end if HTTPS on
return None
def GenPasswd(length=8, chars=string.letters + string.digits):
@@ -244,7 +252,7 @@ class AutoLoginBackend:
user = User(username=username, password=GenPasswd())
user.is_staff = False
user.is_superuser = False
- # get first, last name
+ # get first, last name
name = os.environ.get('SSL_CLIENT_S_DN_CN').strip()
if not name.count(' '):
user.first_name = " "
@@ -266,7 +274,7 @@ class AutoLoginBackend:
tui = user.get_profile()
tui.middle_name = mn
tui.role = 'Misc'
- tui.section = None# no section assigned at first
+ tui.section = None# no section assigned at first
tui.save()
# return None
return user
@@ -274,7 +282,7 @@ class AutoLoginBackend:
def get_user(self, user_id):
if not os.environ.has_key('HTTPS'):
return None
- if not os.environ.get('HTTPS')=='on':# only use this back-end if HTTPS on
+ if not os.environ.get('HTTPS') == 'on':# only use this back-end if HTTPS on
return None
try:
return User.objects.get(pk=user_id)
diff --git a/lms/djangoapps/static_template_view/views.py b/lms/djangoapps/static_template_view/views.py
index 5161532bb6..90087e06d6 100644
--- a/lms/djangoapps/static_template_view/views.py
+++ b/lms/djangoapps/static_template_view/views.py
@@ -1,4 +1,4 @@
-# View for semi-static templatized content.
+# View for semi-static templatized content.
#
# List of valid templates is explicitly managed for (short-term)
# security reasons.
@@ -8,23 +8,20 @@ from django.shortcuts import redirect
from django.conf import settings
from django_future.csrf import ensure_csrf_cookie
-from util.cache import cache_if_anonymous
+from util.cache import cache_if_anonymous
valid_templates = []
if settings.STATIC_GRAB:
- valid_templates = valid_templates+['server-down.html',
+ valid_templates = valid_templates + ['server-down.html',
'server-error.html'
- 'server-overloaded.html',
- 'mitx_global.html',
- 'mitx-overview.html',
- '6002x-faq.html',
- '6002x-press-release.html'
+ 'server-overloaded.html',
]
-def index(request, template):
+
+def index(request, template):
if template in valid_templates:
- return render_to_response('static_templates/' + template, {})
+ return render_to_response('static_templates/' + template, {})
else:
return redirect('/')
@@ -36,19 +33,16 @@ def render(request, template):
This view function renders the template sent without checking that it
exists. Do not expose template as a regex part of the url. The user should
not be able to ender any arbitray template name. The correct usage would be:
-
+
url(r'^jobs$', 'static_template_view.views.render', {'template': 'jobs.html'}, name="jobs")
- """
+ """
return render_to_response('static_templates/' + template, {})
-
-valid_auth_templates=[]
-def auth_index(request, template):
- if not request.user.is_authenticated():
- return redirect('/')
+def render_404(request):
+ return render_to_response('static_templates/404.html', {})
+
+
+def render_500(request):
+ return render_to_response('static_templates/server-error.html', {})
- if template in valid_auth_templates:
- return render_to_response(template,{})
- else:
- return redirect('/')
diff --git a/lms/djangoapps/staticbook/views.py b/lms/djangoapps/staticbook/views.py
index b7633dd1d3..948e66b50c 100644
--- a/lms/djangoapps/staticbook/views.py
+++ b/lms/djangoapps/staticbook/views.py
@@ -3,10 +3,12 @@ from mitxmako.shortcuts import render_to_response
from courseware.courses import check_course
+
@login_required
-def index(request, course_id, page=0):
+def index(request, course_id, page=0):
course = check_course(course_id)
- return render_to_response('staticbook.html',{'page':int(page), 'course': course})
+ return render_to_response('staticbook.html', {'page': int(page), 'course': course})
+
def index_shifted(request, course_id, page):
- return index(request, course_id=course_id, page=int(page)+24)
+ return index(request, course_id=course_id, page=int(page) + 24)
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index c61396d5fd..bd2a0fc389 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -32,11 +32,17 @@ LOG_DIR = ENV_TOKENS['LOG_DIR']
CACHES = ENV_TOKENS['CACHES']
+for feature,value in ENV_TOKENS.get('MITX_FEATURES', {}).items():
+ MITX_FEATURES[feature] = value
+
+WIKI_ENABLED = ENV_TOKENS.get('WIKI_ENABLED',WIKI_ENABLED)
+
LOGGING = get_logger_config(LOG_DIR,
logging_env=ENV_TOKENS['LOGGING_ENV'],
syslog_addr=(ENV_TOKENS['SYSLOG_SERVER'], 514),
debug=False)
+
############################## SECURE AUTH ITEMS ###############################
# Secret things: passwords, access keys, etc.
with open(ENV_ROOT / "auth.json") as auth_file:
diff --git a/lms/envs/common.py b/lms/envs/common.py
index ea07b8030e..093e97c325 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -22,6 +22,9 @@ import sys
import os
import tempfile
import glob2
+import errno
+import hashlib
+from collections import defaultdict
import djcelery
from path import path
@@ -41,6 +44,10 @@ MITX_FEATURES = {
'DISPLAY_HISTOGRAMS_TO_STAFF' : True,
'REROUTE_ACTIVATION_EMAIL' : False, # nonempty string = address for all activation emails
'DEBUG_LEVEL' : 0, # 0 = lowest level, least verbose, 255 = max level, most verbose
+
+ ## DO NOT SET TO True IN THIS FILE
+ ## Doing so will cause all courses to be released on production
+ 'DISABLE_START_DATES': False, # When True, all courses will be active, regardless of start date
}
# Used for A/B testing
@@ -104,6 +111,9 @@ LIB_URL = '/static/js/'
# Dev machines shouldn't need the book
# BOOK_URL = '/static/book/'
BOOK_URL = 'https://mitxstatic.s3.amazonaws.com/book_images/' # For AWS deploys
+# RSS_URL = r'lms/templates/feed.rss'
+# PRESS_URL = r''
+RSS_TIMEOUT = 600
# Configuration option for when we want to grab server error pages
STATIC_GRAB = False
@@ -144,6 +154,7 @@ MODULESTORE = {
'OPTIONS': {
'data_dir': DATA_DIR,
'default_class': 'xmodule.hidden_module.HiddenDescriptor',
+ 'eager': True,
}
}
}
@@ -156,17 +167,17 @@ TEMPLATE_DEBUG = False
# Site info
SITE_ID = 1
-SITE_NAME = "localhost:8000"
+SITE_NAME = "edx.org"
HTTPS = 'on'
ROOT_URLCONF = 'lms.urls'
IGNORABLE_404_ENDS = ('favicon.ico')
# Email
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
-DEFAULT_FROM_EMAIL = 'registration@mitx.mit.edu'
-DEFAULT_FEEDBACK_EMAIL = 'feedback@mitx.mit.edu'
+DEFAULT_FROM_EMAIL = 'registration@edx.org'
+DEFAULT_FEEDBACK_EMAIL = 'feedback@edx.org'
ADMINS = (
- ('MITx Admins', 'admin@mitx.mit.edu'),
+ ('edX Admins', 'admin@edx.org'),
)
MANAGERS = ADMINS
@@ -176,6 +187,7 @@ ADMIN_MEDIA_PREFIX = '/static/admin/'
STATIC_ROOT = ENV_ROOT / "staticfiles"
STATICFILES_DIRS = [
+ COMMON_ROOT / "static",
PROJECT_ROOT / "static",
ASKBOT_ROOT / "askbot" / "skins",
@@ -260,7 +272,6 @@ TEMPLATE_LOADERS = (
MIDDLEWARE_CLASSES = (
'util.middleware.ExceptionLoggingMiddleware',
- 'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@@ -292,15 +303,127 @@ PIPELINE_CSS = {
'source_filenames': ['sass/application.scss'],
'output_filename': 'css/application.css',
},
+ 'course': {
+ 'source_filenames': ['sass/course.scss', 'js/vendor/CodeMirror/codemirror.css', 'css/vendor/jquery.treeview.css'],
+ 'output_filename': 'css/course.css',
+ },
+ 'ie-fixes': {
+ 'source_filenames': ['sass/ie.scss'],
+ 'output_filename': 'css/ie.css',
+ },
}
-PIPELINE_ALWAYS_RECOMPILE = ['sass/application.scss']
+PIPELINE_ALWAYS_RECOMPILE = ['sass/application.scss', 'sass/ie.scss', 'sass/course.scss']
+
+courseware_only_js = [
+ PROJECT_ROOT / 'static/coffee/src/' + pth + '.coffee'
+ for pth
+ in ['courseware', 'histogram', 'navigation', 'time', ]
+]
+courseware_only_js += [
+ pth for pth
+ in glob2.glob(PROJECT_ROOT / 'static/coffee/src/modules/**/*.coffee')
+]
+
+main_vendor_js = [
+ 'js/vendor/jquery.min.js',
+ 'js/vendor/jquery-ui.min.js',
+ 'js/vendor/swfobject/swfobject.js',
+ 'js/vendor/jquery.cookie.js',
+ 'js/vendor/jquery.qtip.min.js',
+]
+
+# Load javascript from all of the available xmodules, and
+# prep it for use in pipeline js
+from xmodule.x_module import XModuleDescriptor
+from xmodule.hidden_module import HiddenDescriptor
+js_file_dir = PROJECT_ROOT / "static" / "coffee" / "module"
+css_file_dir = PROJECT_ROOT / "static" / "sass" / "module"
+module_styles_path = css_file_dir / "_module-styles.scss"
+
+for dir_ in (js_file_dir, css_file_dir):
+ try:
+ os.makedirs(dir_)
+ except OSError as exc:
+ if exc.errno == errno.EEXIST:
+ pass
+ else:
+ raise
+
+js_fragments = set()
+css_fragments = defaultdict(set)
+for descriptor in XModuleDescriptor.load_classes() + [HiddenDescriptor]:
+ module_js = descriptor.module_class.get_javascript()
+ for filetype in ('coffee', 'js'):
+ for idx, fragment in enumerate(module_js.get(filetype, [])):
+ js_fragments.add((idx, filetype, fragment))
+
+ module_css = descriptor.module_class.get_css()
+ for filetype in ('sass', 'scss', 'css'):
+ for idx, fragment in enumerate(module_css.get(filetype, [])):
+ css_fragments[idx, filetype, fragment].add(descriptor.module_class.__name__)
+
+module_js_sources = []
+for idx, filetype, fragment in sorted(js_fragments):
+ path = js_file_dir / "{idx}-{hash}.{type}".format(
+ idx=idx,
+ hash=hashlib.md5(fragment).hexdigest(),
+ type=filetype)
+ with open(path, 'w') as js_file:
+ js_file.write(fragment)
+ module_js_sources.append(path.replace(PROJECT_ROOT / "static/", ""))
+
+css_imports = defaultdict(set)
+for (idx, filetype, fragment), classes in sorted(css_fragments.items()):
+ fragment_name = "{idx}-{hash}.{type}".format(
+ idx=idx,
+ hash=hashlib.md5(fragment).hexdigest(),
+ type=filetype)
+ # Prepend _ so that sass just includes the files into a single file
+ with open(css_file_dir / '_' + fragment_name, 'w') as js_file:
+ js_file.write(fragment)
+
+ for class_ in classes:
+ css_imports[class_].add(fragment_name)
+
+with open(module_styles_path, 'w') as module_styles:
+ for class_, fragment_names in css_imports.items():
+ imports = "\n".join('@import "{0}";'.format(name) for name in fragment_names)
+ module_styles.write(""".xmodule_{class_} {{ {imports} }}""".format(
+ class_=class_, imports=imports
+ ))
PIPELINE_JS = {
'application': {
- 'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee')],
+ # Application will contain all paths not in courseware_only_js
+ 'source_filenames': [
+ pth.replace(COMMON_ROOT / 'static/', '')
+ for pth
+ in glob2.glob(COMMON_ROOT / 'static/coffee/src/**/*.coffee')
+ ] + [
+ pth.replace(PROJECT_ROOT / 'static/', '')
+ for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee')\
+ if pth not in courseware_only_js
+ ] + [
+ 'js/form.ext.js',
+ 'js/my_courses_dropdown.js',
+ 'js/toggle_login_modal.js',
+ 'js/sticky_filter.js',
+ ],
'output_filename': 'js/application.js'
},
+ 'courseware': {
+ 'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in courseware_only_js],
+ 'output_filename': 'js/courseware.js'
+ },
+ 'main_vendor': {
+ 'source_filenames': main_vendor_js,
+ 'output_filename': 'js/main_vendor.js',
+ },
+ 'module-js': {
+ 'source_filenames': module_js_sources,
+ 'output_filename': 'js/modules.js',
+ },
'spec': {
'source_filenames': [pth.replace(PROJECT_ROOT / 'static/', '') for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/spec/**/*.coffee')],
'output_filename': 'js/spec.js'
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index ff4e853c12..1a2659cb1f 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -13,6 +13,8 @@ from .logsettings import get_logger_config
DEBUG = True
TEMPLATE_DEBUG = True
+MITX_FEATURES['DISABLE_START_DATES'] = True
+
WIKI_ENABLED = True
LOGGING = get_logger_config(ENV_ROOT / "log",
@@ -54,8 +56,8 @@ CACHES = {
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
################################ DEBUG TOOLBAR #################################
-#INSTALLED_APPS += ('debug_toolbar',)
-#MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
+INSTALLED_APPS += ('debug_toolbar',)
+MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
INTERNAL_IPS = ('127.0.0.1',)
DEBUG_TOOLBAR_PANELS = (
diff --git a/lms/envs/dev_mongo.py b/lms/envs/dev_mongo.py
new file mode 100644
index 0000000000..1c4980a538
--- /dev/null
+++ b/lms/envs/dev_mongo.py
@@ -0,0 +1,19 @@
+"""
+This config file runs the dev environment, but with mongo as the datastore
+"""
+from .dev import *
+
+GITHUB_REPO_ROOT = ENV_ROOT / "data"
+
+MODULESTORE = {
+ 'default': {
+ 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
+ 'OPTIONS': {
+ 'default_class': 'xmodule.raw_module.RawDescriptor',
+ 'host': 'localhost',
+ 'db': 'xmodule',
+ 'collection': 'modulestore',
+ 'fs_root': GITHUB_REPO_ROOT,
+ }
+ }
+}
diff --git a/lms/envs/test.py b/lms/envs/test.py
index d52468989e..fdfbfb20c4 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -46,6 +46,19 @@ LOGGING = get_logger_config(TEST_ROOT / "log",
tracking_filename="tracking.log",
debug=True)
+COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data"
+
+# TODO (cpennington): We need to figure out how envs/test.py can inject things into common.py so that we don't have to repeat this sort of thing
+STATICFILES_DIRS = [
+ COMMON_ROOT / "static",
+ PROJECT_ROOT / "static",
+]
+STATICFILES_DIRS += [
+ (course_dir, COMMON_TEST_DATA_ROOT / course_dir)
+ for course_dir in os.listdir(COMMON_TEST_DATA_ROOT)
+ if os.path.isdir(COMMON_TEST_DATA_ROOT / course_dir)
+]
+
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
diff --git a/lms/lib/dogfood/check.py b/lms/lib/dogfood/check.py
index 6eb34d9976..0a1da38529 100644
--- a/lms/lib/dogfood/check.py
+++ b/lms/lib/dogfood/check.py
@@ -8,34 +8,36 @@ from django.conf import settings
import capa.capa_problem as lcp
from dogfood.views import update_problem
+
def GenID(length=8, chars=string.letters + string.digits):
return ''.join([choice(chars) for i in range(length)])
randomid = GenID()
-def check_problem_code(ans,the_lcp,correct_answers,false_answers):
+
+def check_problem_code(ans, the_lcp, correct_answers, false_answers):
"""
ans = student's answer
the_lcp = LoncapaProblem instance
-
+
returns dict {'ok':is_ok,'msg': message with iframe}
"""
pfn = "dog%s" % randomid
- pfn += the_lcp.problem_id.replace('filename','') # add problem ID to dogfood problem name
- update_problem(pfn,ans,filestore=the_lcp.system.filestore)
+ pfn += the_lcp.problem_id.replace('filename', '') # add problem ID to dogfood problem name
+ update_problem(pfn, ans, filestore=the_lcp.system.filestore)
msg = ' '
- msg += '' % (settings.MITX_ROOT_URL,pfn)
+ msg += '' % (settings.MITX_ROOT_URL, pfn)
msg += ' '
endmsg = """Note: if the code text box disappears after clicking on "Check",
- please type something in the box to make it refresh properly. This is a
+ please type something in the box to make it refresh properly. This is a
bug with Chrome; it does not happen with Firefox. It is being fixed.
"""
is_ok = True
if (not correct_answers) or (not false_answers):
- ret = {'ok':is_ok,
- 'msg': msg+endmsg,
+ ret = {'ok': is_ok,
+ 'msg': msg + endmsg,
}
return ret
@@ -43,19 +45,19 @@ def check_problem_code(ans,the_lcp,correct_answers,false_answers):
# check correctness
fp = the_lcp.system.filestore.open('problems/%s.xml' % pfn)
test_lcp = lcp.LoncapaProblem(fp, '1', system=the_lcp.system)
-
- if not (test_lcp.grade_answers(correct_answers).get_correctness('1_2_1')=='correct'):
+
+ if not (test_lcp.grade_answers(correct_answers).get_correctness('1_2_1') == 'correct'):
is_ok = False
- if (test_lcp.grade_answers(false_answers).get_correctness('1_2_1')=='correct'):
+ if (test_lcp.grade_answers(false_answers).get_correctness('1_2_1') == 'correct'):
is_ok = False
- except Exception,err:
+ except Exception, err:
is_ok = False
- msg += "Error: %s
" % str(err).replace('<','<')
- msg += "
%s " % traceback.format_exc().replace('<','<')
-
- ret = {'ok':is_ok,
- 'msg': msg+endmsg,
+ msg += "Error: %s
" % str(err).replace('<', '<')
+ msg += "
%s " % traceback.format_exc().replace('<', '<')
+
+ ret = {'ok': is_ok,
+ 'msg': msg + endmsg,
}
return ret
-
-
+
+
diff --git a/lms/lib/dogfood/views.py b/lms/lib/dogfood/views.py
index d621603f31..7a0e03436d 100644
--- a/lms/lib/dogfood/views.py
+++ b/lms/lib/dogfood/views.py
@@ -3,12 +3,12 @@ dogfood.py
For using mitx / edX / i4x in checking itself.
-df_capa_problem: accepts an XML file for a problem, and renders it.
+df_capa_problem: accepts an XML file for a problem, and renders it.
'''
import logging
import datetime
import re
-import os # FIXME - use OSFS instead
+import os # FIXME - use OSFS instead
from fs.osfs import OSFS
@@ -25,7 +25,7 @@ import track.views
from lxml import etree
-from courseware.module_render import make_track_function, I4xSystem, get_module
+from courseware.module_render import make_track_function, ModuleSystem, get_module
from courseware.models import StudentModule
from multicourse import multicourse_settings
from student.models import UserProfile
@@ -39,29 +39,31 @@ import xmodule
log = logging.getLogger("mitx.courseware")
etree.set_default_parser(etree.XMLParser(dtd_validation=False, load_dtd=False,
- remove_comments = True))
+ remove_comments=True))
-DOGFOOD_COURSENAME = 'edx_dogfood' # FIXME - should not be here; maybe in settings
+DOGFOOD_COURSENAME = 'edx_dogfood' # FIXME - should not be here; maybe in settings
-def update_problem(pfn,pxml,coursename=None,overwrite=True,filestore=None):
+
+def update_problem(pfn, pxml, coursename=None, overwrite=True, filestore=None):
'''
update problem with filename pfn, and content (xml) pxml.
'''
if not filestore:
if not coursename: coursename = DOGFOOD_COURSENAME
- xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
+ xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
pfn2 = settings.DATA_DIR + xp + 'problems/%s.xml' % pfn
- fp = open(pfn2,'w')
+ fp = open(pfn2, 'w')
else:
pfn2 = 'problems/%s.xml' % pfn
- fp = filestore.open(pfn2,'w')
+ fp = filestore.open(pfn2, 'w')
log.debug('[dogfood.update_problem] pfn2=%s' % pfn2)
if os.path.exists(pfn2) and not overwrite: return # don't overwrite if already exists and overwrite=False
- pxmls = pxml if type(pxml) in [str,unicode] else etree.tostring(pxml,pretty_print=True)
+ pxmls = pxml if type(pxml) in [str, unicode] else etree.tostring(pxml, pretty_print=True)
fp.write(pxmls)
fp.close()
+
def df_capa_problem(request, id=None):
'''
dogfood capa problem.
@@ -70,7 +72,7 @@ def df_capa_problem(request, id=None):
Returns rendered problem.
'''
# "WARNING: UNDEPLOYABLE CODE. FOR DEV USE ONLY."
-
+
if settings.DEBUG:
log.debug('[lib.dogfood.df_capa_problem] id=%s' % id)
@@ -79,58 +81,58 @@ def df_capa_problem(request, id=None):
else:
coursename = request.session['coursename']
- xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
+ xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
# Grab the XML corresponding to the request from course.xml
module = 'problem'
try:
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
- except Exception,err:
+ except Exception, err:
log.error("[lib.dogfood.df_capa_problem] error in calling content_parser: %s" % err)
xml = None
# if problem of given ID does not exist, then create it
# do this only if course.xml has a section named "DogfoodProblems"
if not xml:
- m = re.match('filename([A-Za-z0-9_]+)$',id) # extract problem filename from ID given
+ m = re.match('filename([A-Za-z0-9_]+)$', id) # extract problem filename from ID given
if not m:
- raise Exception,'[lib.dogfood.df_capa_problem] Illegal problem id %s' % id
+ raise Exception, '[lib.dogfood.df_capa_problem] Illegal problem id %s' % id
pfn = m.group(1)
log.debug('[lib.dogfood.df_capa_problem] creating new problem pfn=%s' % pfn)
# add problem to course.xml
fn = settings.DATA_DIR + xp + 'course.xml'
xml = etree.parse(fn)
- seq = xml.find('chapter/section[@name="DogfoodProblems"]/sequential') # assumes simplistic course.xml structure!
- if seq==None:
- raise Exception,"[lib.dogfood.views.df_capa_problem] missing DogfoodProblems section in course.xml!"
+ seq = xml.find('chapter/section[@name="DogfoodProblems"]/sequential') # assumes simplistic course.xml structure!
+ if seq == None:
+ raise Exception, "[lib.dogfood.views.df_capa_problem] missing DogfoodProblems section in course.xml!"
newprob = etree.Element('problem')
- newprob.set('type','lecture')
- newprob.set('showanswer','attempted')
- newprob.set('rerandomize','never')
- newprob.set('title',pfn)
- newprob.set('filename',pfn)
- newprob.set('name',pfn)
+ newprob.set('type', 'lecture')
+ newprob.set('showanswer', 'attempted')
+ newprob.set('rerandomize', 'never')
+ newprob.set('title', pfn)
+ newprob.set('filename', pfn)
+ newprob.set('name', pfn)
seq.append(newprob)
- fp = open(fn,'w')
- fp.write(etree.tostring(xml,pretty_print=True)) # write new XML
+ fp = open(fn, 'w')
+ fp.write(etree.tostring(xml, pretty_print=True)) # write new XML
fp.close()
# now create new problem file
# update_problem(pfn,'\n\nThis is a new problem\n \n \n',coursename,overwrite=False)
-
+
# reset cache entry
user = request.user
groups = content_parser.user_groups(user)
- options = {'dev_content':settings.DEV_CONTENT,
- 'groups' : groups}
+ options = {'dev_content': settings.DEV_CONTENT,
+ 'groups': groups}
filename = xp + 'course.xml'
cache_key = filename + "_processed?dev_content:" + str(options['dev_content']) + "&groups:" + str(sorted(groups))
log.debug('[lib.dogfood.df_capa_problem] cache_key = %s' % cache_key)
#cache.delete(cache_key)
- tree = content_parser.course_xml_process(xml) # add ID tags
- cache.set(cache_key,etree.tostring(tree),60)
+ tree = content_parser.course_xml_process(xml) # add ID tags
+ cache.set(cache_key, etree.tostring(tree), 60)
# settings.DEFAULT_GROUPS.append('dev') # force content_parser.course_file to not use cache
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
@@ -141,9 +143,10 @@ def df_capa_problem(request, id=None):
request.session['dogfood_id'] = id
# hand over to quickedit to do the rest
- return quickedit(request,id=id,qetemplate='dogfood.html',coursename=coursename)
+ return quickedit(request, id=id, qetemplate='dogfood.html', coursename=coursename)
-def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
+
+def quickedit(request, id=None, qetemplate='quickedit.html', coursename=None):
'''
quick-edit capa problem.
@@ -159,27 +162,27 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
print "a load balanacer"
if not request.user.is_staff:
- if not ('dogfood_id' in request.session and request.session['dogfood_id']==id):
+ if not ('dogfood_id' in request.session and request.session['dogfood_id'] == id):
return redirect('/')
- if id=='course.xml':
+ if id == 'course.xml':
return quickedit_git_reload(request)
# get coursename if stored
if not coursename:
coursename = multicourse_settings.get_coursename_from_request(request)
- xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
+ xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
- def get_lcp(coursename,id):
+ def get_lcp(coursename, id):
# Grab the XML corresponding to the request from course.xml
# create empty student state for this problem, if not previously existing
- s = StudentModule.objects.filter(student=request.user,
+ s = StudentModule.objects.filter(student=request.user,
module_id=id)
student_module_cache = list(s) if s is not None else []
#if len(s) == 0 or s is None:
- # smod=StudentModule(student=request.user,
+ # smod=StudentModule(student=request.user,
# module_type = 'problem',
- # module_id=id,
+ # module_id=id,
# state=instance.get_state())
# smod.save()
# student_module_cache = [smod]
@@ -187,29 +190,28 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
module_xml = etree.XML(content_parser.module_xml(request.user, module, 'id', id, coursename))
module_id = module_xml.get('id')
log.debug("module_id = %s" % module_id)
- (instance,smod,module_type) = get_module(request.user, request, module_xml, student_module_cache, position=None)
+ (instance, smod, module_type) = get_module(request.user, request, module_xml, student_module_cache, position=None)
log.debug('[dogfood.views] instance=%s' % instance)
lcp = instance.lcp
log.debug('[dogfood.views] lcp=%s' % lcp)
pxml = lcp.tree
- pxmls = etree.tostring(pxml,pretty_print=True)
+ pxmls = etree.tostring(pxml, pretty_print=True)
return instance, pxmls
- def old_get_lcp(coursename,id):
+ def old_get_lcp(coursename, id):
# Grab the XML corresponding to the request from course.xml
module = 'problem'
xml = content_parser.module_xml(request.user, module, 'id', id, coursename)
-
- ajax_url = settings.MITX_ROOT_URL + '/modx/'+id+'/'
-
+
+ ajax_url = settings.MITX_ROOT_URL + '/modx/' + id + '/'
+
# Create the module (instance of capa_module.Module)
- system = I4xSystem(track_function = make_track_function(request),
- render_function = None,
- render_template = render_to_string,
- ajax_url = ajax_url,
- filestore = OSFS(settings.DATA_DIR + xp),
- #role = 'staff' if request.user.is_staff else 'student', # TODO: generalize this
- )
+ system = ModuleSystem(track_function=make_track_function(request),
+ render_function=None,
+ render_template=render_to_string,
+ ajax_url=ajax_url,
+ filestore=OSFS(settings.DATA_DIR + xp),
+ )
instance = xmodule.get_module_class(module)(system,
xml,
id,
@@ -240,7 +242,7 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
action = request.POST['qesubmit']
if "Revert" in action:
msg = "Reverted to original"
- elif action=='Change Problem':
+ elif action == 'Change Problem':
key = 'quickedit_%s' % id
if not key in request.POST:
msg = "oops, missing code key=%s" % key
@@ -248,7 +250,7 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
newcode = request.POST[key]
# see if code changed
- if str(newcode)==str(pxmls) or '\n'+str(newcode)==str(pxmls):
+ if str(newcode) == str(pxmls) or '\n' + str(newcode) == str(pxmls):
msg = "No changes"
else:
# check new code
@@ -256,16 +258,16 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
try:
newxml = etree.fromstring(newcode)
isok = True
- except Exception,err:
+ except Exception, err:
msg = "Failed to change problem: XML error \"%s \"" % err
-
+
if isok:
filename = instance.lcp.fileobject.name
- fp = open(filename,'w') # TODO - replace with filestore call?
+ fp = open(filename, 'w') # TODO - replace with filestore call?
fp.write(newcode)
fp.close()
- msg = "Problem changed! (%s )" % filename
- instance, pxmls = get_lcp(coursename,id)
+ msg = "Problem changed! (%s )" % filename
+ instance, pxmls = get_lcp(coursename, id)
lcp = instance.lcp
@@ -273,20 +275,21 @@ def quickedit(request, id=None, qetemplate='quickedit.html',coursename=None):
phtml = instance.get_html()
# phtml = instance.get_problem_html()
- context = {'id':id,
- 'msg' : msg,
- 'lcp' : lcp,
- 'filename' : lcp.fileobject.name,
- 'pxmls' : pxmls,
- 'phtml' : phtml,
- "destroy_js":'',
- 'init_js':'',
- 'csrf':csrf(request)['csrf_token'],
+ context = {'id': id,
+ 'msg': msg,
+ 'lcp': lcp,
+ 'filename': lcp.fileobject.name,
+ 'pxmls': pxmls,
+ 'phtml': phtml,
+ "destroy_js": '',
+ 'init_js': '',
+ 'csrf': csrf(request)['csrf_token'],
}
result = render_to_response(qetemplate, context)
return result
+
def quickedit_git_reload(request):
'''
reload course.xml and all courseware files for this course, from the git repo.
@@ -298,25 +301,25 @@ def quickedit_git_reload(request):
# get coursename if stored
coursename = multicourse_settings.get_coursename_from_request(request)
- xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
+ xp = multicourse_settings.get_course_xmlpath(coursename) # path to XML for the course
msg = ""
if 'cancel' in request.POST:
return redirect("/courseware")
-
+
if 'gitupdate' in request.POST:
import os # FIXME - put at top?
#cmd = "cd ../data%s; git reset --hard HEAD; git pull origin %s" % (xp,xp.replace('/',''))
- cmd = "cd ../data%s; ./GITRELOAD '%s'" % (xp,xp.replace('/',''))
+ cmd = "cd ../data%s; ./GITRELOAD '%s'" % (xp, xp.replace('/', ''))
msg += 'cmd: %s
' % cmd
ret = os.popen(cmd).read()
- msg += '
%s ' % ret.replace('<','<')
+ msg += '
%s ' % ret.replace('<', '<')
msg += "git update done!
"
- context = {'id':id,
- 'msg' : msg,
- 'coursename' : coursename,
- 'csrf':csrf(request)['csrf_token'],
+ context = {'id': id,
+ 'msg': msg,
+ 'coursename': coursename,
+ 'csrf': csrf(request)['csrf_token'],
}
result = render_to_response("gitupdate.html", context)
diff --git a/lms/lib/loncapa/loncapa_check.py b/lms/lib/loncapa/loncapa_check.py
index 961e24ea70..0fd998e00e 100644
--- a/lms/lib/loncapa/loncapa_check.py
+++ b/lms/lib/loncapa/loncapa_check.py
@@ -9,28 +9,29 @@ from __future__ import division
import random
import math
-def lc_random(lower,upper,stepsize):
+
+def lc_random(lower, upper, stepsize):
'''
like random.randrange but lower and upper can be non-integer
'''
- nstep = int((upper-lower)/(1.0*stepsize))
- choices = [lower+x*stepsize for x in range(nstep)]
+ nstep = int((upper - lower) / (1.0 * stepsize))
+ choices = [lower + x * stepsize for x in range(nstep)]
return random.choice(choices)
-def lc_choose(index,*args):
+
+def lc_choose(index, *args):
'''
return args[index]
'''
try:
- return args[int(index)-1]
- except Exception,err:
+ return args[int(index) - 1]
+ except Exception, err:
pass
if len(args):
return args[0]
- raise Exception,"loncapa_check.lc_choose error, index=%s, args=%s" % (index,args)
-
-deg2rad = math.pi/180.0
-rad2deg = 180.0/math.pi
+ raise Exception, "loncapa_check.lc_choose error, index=%s, args=%s" % (index, args)
+deg2rad = math.pi / 180.0
+rad2deg = 180.0 / math.pi
diff --git a/lms/lib/newrelic_logging/__init__.py b/lms/lib/newrelic_logging/__init__.py
index 2c5749e3fa..24101a5091 100644
--- a/lms/lib/newrelic_logging/__init__.py
+++ b/lms/lib/newrelic_logging/__init__.py
@@ -2,6 +2,7 @@ import newrelic.agent
import logging
+
class NewRelicHandler(logging.Handler):
def emit(self, record):
if record.exc_info is not None:
diff --git a/lms/lib/perfstats/middleware.py b/lms/lib/perfstats/middleware.py
index 1308dd650a..7d3880976c 100644
--- a/lms/lib/perfstats/middleware.py
+++ b/lms/lib/perfstats/middleware.py
@@ -7,23 +7,24 @@ from django.db import connection
import views
-class ProfileMiddleware:
- def process_request (self, request):
+
+class ProfileMiddleware:
+ def process_request(self, request):
self.t = time.time()
print "Process request"
- def process_response (self, request, response):
+ def process_response(self, request, response):
# totalTime = time.time() - self.t
# tmpfile = tempfile.NamedTemporaryFile(prefix='sqlprof-t=' + str(totalTime) + "-", delete=False)
-
+
# output = ""
# for query in connection.queries:
# output += "Time: " + str(query['time']) + "\nQuery: " + query['sql'] + "\n\n"
-
+
# tmpfile.write(output)
-
+
# print "SQL Log file: " , tmpfile.name
# tmpfile.close()
-
+
# print "Process response"
return response
diff --git a/lms/lib/perfstats/views.py b/lms/lib/perfstats/views.py
index 7d0695b9fe..e4cf3dd5b9 100644
--- a/lms/lib/perfstats/views.py
+++ b/lms/lib/perfstats/views.py
@@ -3,6 +3,7 @@ import middleware
from django.http import HttpResponse
+
def end_profile(request):
names = middleware.restart_profile()
return HttpResponse(str(names))
diff --git a/lms/lib/symmath/formula.py b/lms/lib/symmath/formula.py
index b7cf2cdb05..4aa9f60d30 100644
--- a/lms/lib/symmath/formula.py
+++ b/lms/lib/symmath/formula.py
@@ -9,7 +9,10 @@
# Acceptes Presentation MathML, Content MathML (and could also do OpenMath)
# Provides sympy representation.
-import os, sys, string, re
+import os
+import sys
+import string
+import re
import logging
import operator
import sympy
@@ -39,22 +42,25 @@ os.environ['PYTHONIOENCODING'] = 'utf-8'
#-----------------------------------------------------------------------------
-class dot(sympy.operations.LatticeOp): # my dot product
+
+class dot(sympy.operations.LatticeOp): # my dot product
zero = sympy.Symbol('dotzero')
identity = sympy.Symbol('dotidentity')
#class dot(sympy.Mul): # my dot product
# is_Mul = False
-def _print_dot(self,expr):
- return '{((%s) \cdot (%s))}' % (expr.args[0],expr.args[1])
+
+def _print_dot(self, expr):
+ return '{((%s) \cdot (%s))}' % (expr.args[0], expr.args[1])
LatexPrinter._print_dot = _print_dot
#-----------------------------------------------------------------------------
# unit vectors (for 8.02)
-def _print_hat(self,expr): return '\\hat{%s}' % str(expr.args[0]).lower()
+
+def _print_hat(self, expr): return '\\hat{%s}' % str(expr.args[0]).lower()
LatexPrinter._print_hat = _print_hat
StrPrinter._print_hat = _print_hat
@@ -62,18 +68,20 @@ StrPrinter._print_hat = _print_hat
#-----------------------------------------------------------------------------
# helper routines
+
def to_latex(x):
- if x==None: return ''
+ if x == None: return ''
# LatexPrinter._print_dot = _print_dot
xs = latex(x)
- xs = xs.replace(r'\XI','XI') # workaround for strange greek
+ xs = xs.replace(r'\XI', 'XI') # workaround for strange greek
#return '%s{}{} ' % (xs[1:-1])
- if xs[0]=='$':
- return '[mathjax]%s[/mathjax] ' % (xs[1:-1]) # for sympy v6
+ if xs[0] == '$':
+ return '[mathjax]%s[/mathjax] ' % (xs[1:-1]) # for sympy v6
return '[mathjax]%s[/mathjax] ' % (xs) # for sympy v7
-def my_evalf(expr,chop=False):
- if type(expr)==list:
+
+def my_evalf(expr, chop=False):
+ if type(expr) == list:
try:
return [x.evalf(chop=chop) for x in expr]
except:
@@ -86,51 +94,52 @@ def my_evalf(expr,chop=False):
#-----------------------------------------------------------------------------
# my version of sympify to import expression into sympy
-def my_sympify(expr,normphase=False,matrix=False,abcsym=False,do_qubit=False,symtab=None):
+
+def my_sympify(expr, normphase=False, matrix=False, abcsym=False, do_qubit=False, symtab=None):
# make all lowercase real?
if symtab:
varset = symtab
else:
- varset = {'p':sympy.Symbol('p'),
- 'g':sympy.Symbol('g'),
- 'e':sympy.E, # for exp
- 'i':sympy.I, # lowercase i is also sqrt(-1)
- 'Q':sympy.Symbol('Q'), # otherwise it is a sympy "ask key"
+ varset = {'p': sympy.Symbol('p'),
+ 'g': sympy.Symbol('g'),
+ 'e': sympy.E, # for exp
+ 'i': sympy.I, # lowercase i is also sqrt(-1)
+ 'Q': sympy.Symbol('Q'), # otherwise it is a sympy "ask key"
#'X':sympy.sympify('Matrix([[0,1],[1,0]])'),
#'Y':sympy.sympify('Matrix([[0,-I],[I,0]])'),
#'Z':sympy.sympify('Matrix([[1,0],[0,-1]])'),
- 'ZZ':sympy.Symbol('ZZ'), # otherwise it is the PythonIntegerRing
- 'XI':sympy.Symbol('XI'), # otherwise it is the capital \XI
- 'hat':sympy.Function('hat'), # for unit vectors (8.02)
+ 'ZZ': sympy.Symbol('ZZ'), # otherwise it is the PythonIntegerRing
+ 'XI': sympy.Symbol('XI'), # otherwise it is the capital \XI
+ 'hat': sympy.Function('hat'), # for unit vectors (8.02)
}
if do_qubit: # turn qubit(...) into Qubit instance
- varset.update({'qubit':sympy.physics.quantum.qubit.Qubit,
- 'Ket':sympy.physics.quantum.state.Ket,
- 'dot':dot,
- 'bit':sympy.Function('bit'),
+ varset.update({'qubit': sympy.physics.quantum.qubit.Qubit,
+ 'Ket': sympy.physics.quantum.state.Ket,
+ 'dot': dot,
+ 'bit': sympy.Function('bit'),
})
if abcsym: # consider all lowercase letters as real symbols, in the parsing
for letter in string.lowercase:
- if letter in varset: # exclude those already done
+ if letter in varset: # exclude those already done
continue
- varset.update({letter:sympy.Symbol(letter,real=True)})
+ varset.update({letter: sympy.Symbol(letter, real=True)})
- sexpr = sympify(expr,locals=varset)
- if normphase: # remove overall phase if sexpr is a list
- if type(sexpr)==list:
+ sexpr = sympify(expr, locals=varset)
+ if normphase: # remove overall phase if sexpr is a list
+ if type(sexpr) == list:
if sexpr[0].is_number:
ophase = sympy.sympify('exp(-I*arg(%s))' % sexpr[0])
- sexpr = [ sympy.Mul(x,ophase) for x in sexpr ]
+ sexpr = [sympy.Mul(x, ophase) for x in sexpr]
def to_matrix(x): # if x is a list of lists, and is rectangular, then return Matrix(x)
- if not type(x)==list:
+ if not type(x) == list:
return x
for row in x:
- if (not type(row)==list):
+ if (not type(row) == list):
return x
rdim = len(x[0])
for row in x:
- if not len(row)==rdim:
+ if not len(row) == rdim:
return x
return sympy.Matrix(x)
@@ -141,12 +150,13 @@ def my_sympify(expr,normphase=False,matrix=False,abcsym=False,do_qubit=False,sym
#-----------------------------------------------------------------------------
# class for symbolic mathematical formulas
+
class formula(object):
'''
Representation of a mathematical formula object. Accepts mathml math expression for constructing,
and can produce sympy translation. The formula may or may not include an assignment (=).
'''
- def __init__(self,expr,asciimath='',options=None):
+ def __init__(self, expr, asciimath='', options=None):
self.expr = expr.strip()
self.asciimath = asciimath
self.the_cmathml = None
@@ -159,41 +169,41 @@ class formula(object):
def is_mathml(self):
return 'f - 2 "
# this is really terrible for turning into cmathml.
@@ -201,12 +211,12 @@ class formula(object):
def fix_pmathml(xml):
for k in xml:
tag = gettag(k)
- if tag=='mrow':
- if len(k)==2:
- if gettag(k[0])=='mi' and k[0].text in ['f','g'] and gettag(k[1])=='mo':
+ if tag == 'mrow':
+ if len(k) == 2:
+ if gettag(k[0]) == 'mi' and k[0].text in ['f', 'g'] and gettag(k[1]) == 'mo':
idx = xml.index(k)
- xml.insert(idx,deepcopy(k[0])) # drop the container
- xml.insert(idx+1,deepcopy(k[1]))
+ xml.insert(idx, deepcopy(k[0])) # drop the container
+ xml.insert(idx + 1, deepcopy(k[1]))
xml.remove(k)
fix_pmathml(k)
@@ -218,16 +228,16 @@ class formula(object):
def fix_hat(xml):
for k in xml:
tag = gettag(k)
- if tag=='mover':
- if len(k)==2:
- if gettag(k[0])=='mi' and gettag(k[1])=='mo' and str(k[1].text)=='^':
+ if tag == 'mover':
+ if len(k) == 2:
+ if gettag(k[0]) == 'mi' and gettag(k[1]) == 'mo' and str(k[1].text) == '^':
newk = etree.Element('mi')
newk.text = 'hat(%s)' % k[0].text
- xml.replace(k,newk)
- if gettag(k[0])=='mrow' and gettag(k[0][0])=='mi' and gettag(k[1])=='mo' and str(k[1].text)=='^':
+ xml.replace(k, newk)
+ if gettag(k[0]) == 'mrow' and gettag(k[0][0]) == 'mi' and gettag(k[1]) == 'mo' and str(k[1].text) == '^':
newk = etree.Element('mi')
newk.text = 'hat(%s)' % k[0][0].text
- xml.replace(k,newk)
+ xml.replace(k, newk)
fix_hat(k)
fix_hat(xml)
@@ -240,18 +250,18 @@ class formula(object):
# pre-process the presentation mathml before sending it to snuggletex to convert to content mathml
try:
xml = self.preprocess_pmathml(self.expr)
- except Exception,err:
+ except Exception, err:
return "Error! Cannot process pmathml"
- pmathml = etree.tostring(xml,pretty_print=True)
+ pmathml = etree.tostring(xml, pretty_print=True)
self.the_pmathml = pmathml
# convert to cmathml
- self.the_cmathml = self.GetContentMathML(self.asciimath,pmathml)
+ self.the_cmathml = self.GetContentMathML(self.asciimath, pmathml)
return self.the_cmathml
- cmathml = property(get_content_mathml,None,None,'content MathML representation')
+ cmathml = property(get_content_mathml, None, None, 'content MathML representation')
- def make_sympy(self,xml=None):
+ def make_sympy(self, xml=None):
'''
Return sympy expression for the math formula.
The math formula is converted to Content MathML then that is parsed.
@@ -259,15 +269,15 @@ class formula(object):
if self.the_sympy: return self.the_sympy
- if xml==None: # root
+ if xml == None: # root
if not self.is_mathml():
return my_sympify(self.expr)
if self.is_presentation_mathml():
try:
cmml = self.cmathml
xml = etree.fromstring(str(cmml))
- except Exception,err:
- raise Exception,'Err %s while converting cmathml to xml; cmml=%s' % (err,cmml)
+ except Exception, err:
+ raise Exception, 'Err %s while converting cmathml to xml; cmml=%s' % (err, cmml)
xml = self.fix_greek_in_mathml(xml)
self.the_sympy = self.make_sympy(xml[0])
else:
@@ -277,36 +287,37 @@ class formula(object):
return self.the_sympy
def gettag(x):
- return re.sub('{http://[^}]+}','',x.tag)
+ return re.sub('{http://[^}]+}', '', x.tag)
# simple math
def op_divide(*args):
- if not len(args)==2:
- raise Exception,'divide given wrong number of arguments!'
+ if not len(args) == 2:
+ raise Exception, 'divide given wrong number of arguments!'
# print "divide: arg0=%s, arg1=%s" % (args[0],args[1])
- return sympy.Mul(args[0],sympy.Pow(args[1],-1))
+ return sympy.Mul(args[0], sympy.Pow(args[1], -1))
- def op_plus(*args): return args[0] if len(args)==1 else op_plus(*args[:-1])+args[-1]
- def op_times(*args): return reduce(operator.mul,args)
+ def op_plus(*args): return args[0] if len(args) == 1 else op_plus(*args[:-1]) + args[-1]
+
+ def op_times(*args): return reduce(operator.mul, args)
def op_minus(*args):
- if len(args)==1:
+ if len(args) == 1:
return -args[0]
- if not len(args)==2:
- raise Exception,'minus given wrong number of arguments!'
+ if not len(args) == 2:
+ raise Exception, 'minus given wrong number of arguments!'
#return sympy.Add(args[0],-args[1])
- return args[0]-args[1]
+ return args[0] - args[1]
- opdict = {'plus': op_plus,
- 'divide' : operator.div,
- 'times' : op_times,
- 'minus' : op_minus,
+ opdict = {'plus': op_plus,
+ 'divide': operator.div,
+ 'times': op_times,
+ 'minus': op_minus,
#'plus': sympy.Add,
#'divide' : op_divide,
#'times' : sympy.Mul,
- 'minus' : op_minus,
- 'root' : sympy.sqrt,
- 'power' : sympy.Pow,
+ 'minus': op_minus,
+ 'root': sympy.sqrt,
+ 'power': sympy.Pow,
'sin': sympy.sin,
'cos': sympy.cos,
}
@@ -320,11 +331,11 @@ class formula(object):
Parse , , , and
'''
tag = gettag(xml)
- if tag=='mn': return xml.text
- elif tag=='mi': return xml.text
- elif tag=='msub': return '_'.join([parsePresentationMathMLSymbol(y) for y in xml])
- elif tag=='msup': return '^'.join([parsePresentationMathMLSymbol(y) for y in xml])
- raise Exception,'[parsePresentationMathMLSymbol] unknown tag %s' % tag
+ if tag == 'mn': return xml.text
+ elif tag == 'mi': return xml.text
+ elif tag == 'msub': return '_'.join([parsePresentationMathMLSymbol(y) for y in xml])
+ elif tag == 'msup': return '^'.join([parsePresentationMathMLSymbol(y) for y in xml])
+ raise Exception, '[parsePresentationMathMLSymbol] unknown tag %s' % tag
# parser tree for Content MathML
tag = gettag(xml)
@@ -332,41 +343,41 @@ class formula(object):
# first do compound objects
- if tag=='apply': # apply operator
+ if tag == 'apply': # apply operator
opstr = gettag(xml[0])
if opstr in opdict:
op = opdict[opstr]
- args = [ self.make_sympy(x) for x in xml[1:]]
+ args = [self.make_sympy(x) for x in xml[1:]]
try:
res = op(*args)
- except Exception,err:
+ except Exception, err:
self.args = args
self.op = op
- raise Exception,'[formula] error=%s failed to apply %s to args=%s' % (err,opstr,args)
+ raise Exception, '[formula] error=%s failed to apply %s to args=%s' % (err, opstr, args)
return res
else:
- raise Exception,'[formula]: unknown operator tag %s' % (opstr)
+ raise Exception, '[formula]: unknown operator tag %s' % (opstr)
- elif tag=='list': # square bracket list
- if gettag(xml[0])=='matrix':
+ elif tag == 'list': # square bracket list
+ if gettag(xml[0]) == 'matrix':
return self.make_sympy(xml[0])
else:
- return [ self.make_sympy(x) for x in xml ]
+ return [self.make_sympy(x) for x in xml]
- elif tag=='matrix':
- return sympy.Matrix([ self.make_sympy(x) for x in xml ])
+ elif tag == 'matrix':
+ return sympy.Matrix([self.make_sympy(x) for x in xml])
- elif tag=='vector':
- return [ self.make_sympy(x) for x in xml ]
+ elif tag == 'vector':
+ return [self.make_sympy(x) for x in xml]
# atoms are below
- elif tag=='cn': # number
+ elif tag == 'cn': # number
return sympy.sympify(xml.text)
return float(xml.text)
- elif tag=='ci': # variable (symbol)
- if len(xml)>0 and (gettag(xml[0])=='msub' or gettag(xml[0])=='msup'): # subscript or superscript
+ elif tag == 'ci': # variable (symbol)
+ if len(xml) > 0 and (gettag(xml[0]) == 'msub' or gettag(xml[0]) == 'msup'): # subscript or superscript
usym = parsePresentationMathMLSymbol(xml[0])
sym = sympy.Symbol(str(usym))
else:
@@ -374,42 +385,42 @@ class formula(object):
if 'hat' in usym:
sym = my_sympify(usym)
else:
- if usym=='i': print "options=",self.options
- if usym=='i' and 'imaginary' in self.options: # i = sqrt(-1)
- sym = sympy.I
+ if usym == 'i': print "options=", self.options
+ if usym == 'i' and 'imaginary' in self.options: # i = sqrt(-1)
+ sym = sympy.I
else:
sym = sympy.Symbol(str(usym))
return sym
else: # unknown tag
- raise Exception,'[formula] unknown tag %s' % tag
+ raise Exception, '[formula] unknown tag %s' % tag
- sympy = property(make_sympy,None,None,'sympy representation')
+ sympy = property(make_sympy, None, None, 'sympy representation')
- def GetContentMathML(self,asciimath,mathml):
+ def GetContentMathML(self, asciimath, mathml):
# URL = 'http://192.168.1.2:8080/snuggletex-webapp-1.2.2/ASCIIMathMLUpConversionDemo'
URL = 'http://127.0.0.1:8080/snuggletex-webapp-1.2.2/ASCIIMathMLUpConversionDemo'
if 1:
- payload = {'asciiMathInput':asciimath,
- 'asciiMathML':mathml,
+ payload = {'asciiMathInput': asciimath,
+ 'asciiMathML': mathml,
#'asciiMathML':unicode(mathml).encode('utf-8'),
}
- headers = {'User-Agent':"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13"}
- r = requests.post(URL,data=payload,headers=headers)
+ headers = {'User-Agent': "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13"}
+ r = requests.post(URL, data=payload, headers=headers)
r.encoding = 'utf-8'
ret = r.text
#print "encoding: ",r.encoding
# return ret
-
+
mode = 0
cmathml = []
for k in ret.split('\n'):
if 'conversion to Content MathML' in k:
mode = 1
continue
- if mode==1:
+ if mode == 1:
if 'Maxima Input Form ' in k:
mode = 0
continue
@@ -420,9 +431,10 @@ class formula(object):
# print cmathml
#return unicode(cmathml)
return cmathml
-
+
#-----------------------------------------------------------------------------
+
def test1():
xmlstr = '''
@@ -435,6 +447,7 @@ def test1():
'''
return formula(xmlstr)
+
def test2():
xmlstr = u'''
@@ -444,13 +457,14 @@ def test2():
2
- α
+ α
'''
return formula(xmlstr)
+
def test3():
xmlstr = '''
@@ -467,6 +481,7 @@ def test3():
'''
return formula(xmlstr)
+
def test4():
xmlstr = u'''
@@ -482,6 +497,7 @@ def test4():
'''
return formula(xmlstr)
+
def test5(): # sum of two matrices
xmlstr = u'''
@@ -545,6 +561,7 @@ def test5(): # sum of two matrices
'''
return formula(xmlstr)
+
def test6(): # imaginary numbers
xmlstr = u'''
@@ -555,4 +572,4 @@ def test6(): # imaginary numbers
'''
- return formula(xmlstr,options='imaginaryi')
+ return formula(xmlstr, options='imaginaryi')
diff --git a/lms/lib/symmath/symmath_check.py b/lms/lib/symmath/symmath_check.py
index 5ea428b1d5..4af012d2cb 100644
--- a/lms/lib/symmath/symmath_check.py
+++ b/lms/lib/symmath/symmath_check.py
@@ -8,7 +8,10 @@
#
# Takes in math expressions given as Presentation MathML (from ASCIIMathML), converts to Content MathML using SnuggleTeX
-import os, sys, string, re
+import os
+import sys
+import string
+import re
import traceback
from formula import *
import logging
@@ -20,40 +23,42 @@ log = logging.getLogger(__name__)
#
# This is one of the main entry points to call.
-def symmath_check_simple(expect,ans,adict={},symtab=None,extra_options=None):
+
+def symmath_check_simple(expect, ans, adict={}, symtab=None, extra_options=None):
'''
Check a symbolic mathematical expression using sympy.
The input is an ascii string (not MathML) converted to math using sympy.sympify.
'''
- options = {'__MATRIX__':False,'__ABC__':False,'__LOWER__':False}
+ options = {'__MATRIX__': False, '__ABC__': False, '__LOWER__': False}
if extra_options: options.update(extra_options)
for op in options: # find options in expect string
if op in expect:
- expect = expect.replace(op,'')
+ expect = expect.replace(op, '')
options[op] = True
- expect = expect.replace('__OR__','__or__') # backwards compatibility
+ expect = expect.replace('__OR__', '__or__') # backwards compatibility
if options['__LOWER__']:
expect = expect.lower()
ans = ans.lower()
try:
- ret = check(expect,ans,
+ ret = check(expect, ans,
matrix=options['__MATRIX__'],
abcsym=options['__ABC__'],
symtab=symtab,
)
except Exception, err:
return {'ok': False,
- 'msg': 'Error %s Failed in evaluating check(%s,%s)' % (err,expect,ans)
+ 'msg': 'Error %s Failed in evaluating check(%s,%s)' % (err, expect, ans)
}
return ret
#-----------------------------------------------------------------------------
# pretty generic checking function
-def check(expect,given,numerical=False,matrix=False,normphase=False,abcsym=False,do_qubit=True,symtab=None,dosimplify=False):
+
+def check(expect, given, numerical=False, matrix=False, normphase=False, abcsym=False, do_qubit=True, symtab=None, dosimplify=False):
"""
Returns dict with
@@ -61,51 +66,51 @@ def check(expect,given,numerical=False,matrix=False,normphase=False,abcsym=False
'msg': response message (in HTML)
"expect" may have multiple possible acceptable answers, separated by "__OR__"
-
+
"""
if "__or__" in expect: # if multiple acceptable answers
eset = expect.split('__or__') # then see if any match
for eone in eset:
- ret = check(eone,given,numerical,matrix,normphase,abcsym,do_qubit,symtab,dosimplify)
+ ret = check(eone, given, numerical, matrix, normphase, abcsym, do_qubit, symtab, dosimplify)
if ret['ok']:
return ret
return ret
flags = {}
if "__autonorm__" in expect:
- flags['autonorm']=True
- expect = expect.replace('__autonorm__','')
+ flags['autonorm'] = True
+ expect = expect.replace('__autonorm__', '')
matrix = True
threshold = 1.0e-3
if "__threshold__" in expect:
- (expect,st) = expect.split('__threshold__')
+ (expect, st) = expect.split('__threshold__')
threshold = float(st)
- numerical=True
+ numerical = True
- if str(given)=='' and not (str(expect)==''):
+ if str(given) == '' and not (str(expect) == ''):
return {'ok': False, 'msg': ''}
try:
- xgiven = my_sympify(given,normphase,matrix,do_qubit=do_qubit,abcsym=abcsym,symtab=symtab)
- except Exception,err:
- return {'ok': False,'msg': 'Error %s in evaluating your expression "%s"' % (err,given)}
+ xgiven = my_sympify(given, normphase, matrix, do_qubit=do_qubit, abcsym=abcsym, symtab=symtab)
+ except Exception, err:
+ return {'ok': False, 'msg': 'Error %s in evaluating your expression "%s"' % (err, given)}
try:
- xexpect = my_sympify(expect,normphase,matrix,do_qubit=do_qubit,abcsym=abcsym,symtab=symtab)
- except Exception,err:
- return {'ok': False,'msg': 'Error %s in evaluating OUR expression "%s"' % (err,expect)}
+ xexpect = my_sympify(expect, normphase, matrix, do_qubit=do_qubit, abcsym=abcsym, symtab=symtab)
+ except Exception, err:
+ return {'ok': False, 'msg': 'Error %s in evaluating OUR expression "%s"' % (err, expect)}
- if 'autonorm' in flags: # normalize trace of matrices
+ if 'autonorm' in flags: # normalize trace of matrices
try:
xgiven /= xgiven.trace()
except Exception, err:
- return {'ok': False,'msg': 'Error %s in normalizing trace of your expression %s' % (err,to_latex(xgiven))}
+ return {'ok': False, 'msg': 'Error %s in normalizing trace of your expression %s' % (err, to_latex(xgiven))}
try:
xexpect /= xexpect.trace()
except Exception, err:
- return {'ok': False,'msg': 'Error %s in normalizing trace of OUR expression %s' % (err,to_latex(xexpect))}
+ return {'ok': False, 'msg': 'Error %s in normalizing trace of OUR expression %s' % (err, to_latex(xexpect))}
msg = 'Your expression was evaluated as ' + to_latex(xgiven)
# msg += ' Expected ' + to_latex(xexpect)
@@ -113,34 +118,35 @@ def check(expect,given,numerical=False,matrix=False,normphase=False,abcsym=False
# msg += " flags=%s" % flags
if matrix and numerical:
- xgiven = my_evalf(xgiven,chop=True)
- dm = my_evalf(sympy.Matrix(xexpect)-sympy.Matrix(xgiven),chop=True)
+ xgiven = my_evalf(xgiven, chop=True)
+ dm = my_evalf(sympy.Matrix(xexpect) - sympy.Matrix(xgiven), chop=True)
msg += " = " + to_latex(xgiven)
- if abs(dm.vec().norm().evalf()) expect='%s', given='%s'" % (expect,given) # debugging
# msg += "
dot test " + to_latex(dot(sympy.Symbol('x'),sympy.Symbol('y')))
- return {'ok': False,'msg': msg }
+ return {'ok': False, 'msg': msg}
#-----------------------------------------------------------------------------
# Check function interface, which takes pmathml input
#
# This is one of the main entry points to call.
-def symmath_check(expect,ans,dynamath=None,options=None,debug=None):
+
+def symmath_check(expect, ans, dynamath=None, options=None, debug=None):
'''
Check a symbolic mathematical expression using sympy.
The input may be presentation MathML. Uses formula.
@@ -160,91 +166,91 @@ def symmath_check(expect,ans,dynamath=None,options=None,debug=None):
# parse expected answer
try:
- fexpect = my_sympify(str(expect),matrix=do_matrix,do_qubit=do_qubit)
- except Exception,err:
- msg += 'Error %s in parsing OUR expected answer "%s"
' % (err,expect)
- return {'ok':False,'msg':msg}
+ fexpect = my_sympify(str(expect), matrix=do_matrix, do_qubit=do_qubit)
+ except Exception, err:
+ msg += 'Error %s in parsing OUR expected answer "%s"
' % (err, expect)
+ return {'ok': False, 'msg': msg}
# if expected answer is a number, try parsing provided answer as a number also
try:
- fans = my_sympify(str(ans),matrix=do_matrix,do_qubit=do_qubit)
- except Exception,err:
+ fans = my_sympify(str(ans), matrix=do_matrix, do_qubit=do_qubit)
+ except Exception, err:
fans = None
- if hasattr(fexpect,'is_number') and fexpect.is_number and fans and hasattr(fans,'is_number') and fans.is_number:
- if abs(abs(fans-fexpect)/fexpect)You entered: %s' % to_latex(fans)
- return {'ok':False,'msg':msg}
+ return {'ok': False, 'msg': msg}
- if fexpect==fans:
+ if fexpect == fans:
msg += 'You entered: %s
' % to_latex(fans)
- return {'ok':True,'msg':msg}
+ return {'ok': True, 'msg': msg}
# convert mathml answer to formula
try:
if dynamath:
mmlans = dynamath[0]
- except Exception,err:
+ except Exception, err:
mmlans = None
if not mmlans:
- return {'ok':False,'msg':'[symmath_check] failed to get MathML for input; dynamath=%s' % dynamath}
- f = formula(mmlans,options=options)
-
+ return {'ok': False, 'msg': '[symmath_check] failed to get MathML for input; dynamath=%s' % dynamath}
+ f = formula(mmlans, options=options)
+
# get sympy representation of the formula
# if DEBUG: msg += '
mmlans=%s' % repr(mmlans).replace('<','<')
try:
fsym = f.sympy
msg += 'You entered: %s
' % to_latex(f.sympy)
- except Exception,err:
+ except Exception, err:
log.exception("Error evaluating expression '%s' as a valid equation" % ans)
- msg += "Error %s in evaluating your expression '%s' as a valid equation
" % (str(err).replace('<','<'),
+ msg += "Error %s in evaluating your expression '%s' as a valid equation
" % (str(err).replace('<', '<'),
ans)
if DEBUG:
msg += ' '
msg += 'DEBUG messages:
'
msg += "
%s " % traceback.format_exc()
- msg += 'cmathml=
%s ' % f.cmathml.replace('<','<')
- msg += 'pmathml=
%s ' % mmlans.replace('<','<')
+ msg += 'cmathml=
%s ' % f.cmathml.replace('<', '<')
+ msg += 'pmathml=
%s ' % mmlans.replace('<', '<')
msg += ' '
- return {'ok':False,'msg':msg}
+ return {'ok': False, 'msg': msg}
# compare with expected
- if hasattr(fexpect,'is_number') and fexpect.is_number:
- if hasattr(fsym,'is_number') and fsym.is_number:
- if abs(abs(fsym-fexpect)/fexpect)Expecting a numerical answer!"
msg += "given = %s
" % repr(ans)
msg += "fsym = %s
" % repr(fsym)
# msg += "cmathml =
%s " % str(f.cmathml).replace('<','<')
- return {'ok':False,'msg':msg}
+ return {'ok': False, 'msg': msg}
- if fexpect==fsym:
- return {'ok':True,'msg':msg}
+ if fexpect == fsym:
+ return {'ok': True, 'msg': msg}
- if type(fexpect)==list:
+ if type(fexpect) == list:
try:
- xgiven = my_evalf(fsym,chop=True)
- dm = my_evalf(sympy.Matrix(fexpect)-sympy.Matrix(xgiven),chop=True)
- if abs(dm.vec().norm().evalf())Error - your input vector or matrix has the wrong dimensions"
- return {'ok':False,'msg':msg}
- except Exception,err:
- msg += "Error %s in comparing expected (a list) and your answer
" % str(err).replace('<','<')
+ return {'ok': False, 'msg': msg}
+ except Exception, err:
+ msg += "Error %s in comparing expected (a list) and your answer
" % str(err).replace('<', '<')
if DEBUG: msg += "
%s " % traceback.format_exc()
- return {'ok':False,'msg':msg}
+ return {'ok': False, 'msg': msg}
#diff = (fexpect-fsym).simplify()
#fsym = fsym.simplify()
#fexpect = fexpect.simplify()
try:
- diff = (fexpect-fsym)
- except Exception,err:
+ diff = (fexpect - fsym)
+ except Exception, err:
diff = None
if DEBUG:
@@ -252,17 +258,18 @@ def symmath_check(expect,ans,dynamath=None,options=None,debug=None):
msg += 'DEBUG messages:
'
msg += "Got: %s
" % repr(fsym)
# msg += "
Got: %s" % str([type(x) for x in fsym.atoms()]).replace('<','<')
- msg += "Expecting: %s
" % repr(fexpect).replace('**','^').replace('hat(I)','hat(i)')
+ msg += "Expecting: %s
" % repr(fexpect).replace('**', '^').replace('hat(I)', 'hat(i)')
# msg += "
Expecting: %s" % str([type(x) for x in fexpect.atoms()]).replace('<','<')
if diff:
msg += "Difference: %s
" % to_latex(diff)
msg += ' '
- return {'ok':False,'msg':msg,'ex':fexpect,'got':fsym}
+ return {'ok': False, 'msg': msg, 'ex': fexpect, 'got': fsym}
#-----------------------------------------------------------------------------
# tests
+
def sctest1():
x = "1/2*(1+(k_e* Q* q)/(m *g *h^2))"
y = '''
@@ -306,6 +313,5 @@ def sctest1():
'''.strip()
z = "1/2(1+(k_e* Q* q)/(m *g *h^2))"
- r = sympy_check2(x,z,{'a':z,'a_fromjs':y},'a')
+ r = sympy_check2(x, z, {'a': z, 'a_fromjs': y}, 'a')
return r
-
diff --git a/lms/static/.gitignore b/lms/static/.gitignore
index 93ba314176..03f1cdabff 100644
--- a/lms/static/.gitignore
+++ b/lms/static/.gitignore
@@ -4,4 +4,5 @@
*.swp
*.orig
*.DS_Store
-application.css
\ No newline at end of file
+application.css
+ie.css
\ No newline at end of file
diff --git a/lms/static/coffee/.gitignore b/lms/static/coffee/.gitignore
index a6c7c2852d..bb90193362 100644
--- a/lms/static/coffee/.gitignore
+++ b/lms/static/coffee/.gitignore
@@ -1 +1,2 @@
*.js
+module
diff --git a/lms/static/coffee/files.json b/lms/static/coffee/files.json
index 28b843021f..4721ef58bb 100644
--- a/lms/static/coffee/files.json
+++ b/lms/static/coffee/files.json
@@ -1,9 +1,9 @@
{
"js_files": [
- "/static/js/jquery.min.js",
- "/static/js/jquery-ui.min.js",
- "/static/js/jquery.leanModal.js",
- "/static/js/flot/jquery.flot.js"
+ "/static/js/vendor/jquery.min.js",
+ "/static/js/vendor/jquery-ui.min.js",
+ "/static/js/vendor/jquery.leanModal.min.js",
+ "/static/js/vendor/flot/jquery.flot.js"
],
"static_files": [
"js/application.js"
diff --git a/lms/static/coffee/src/courseware.coffee b/lms/static/coffee/src/courseware.coffee
index 39c4dec2fe..1a8910bb28 100644
--- a/lms/static/coffee/src/courseware.coffee
+++ b/lms/static/coffee/src/courseware.coffee
@@ -5,23 +5,13 @@ class @Courseware
Courseware.prefix = $("meta[name='path_prefix']").attr('content')
new Navigation
Logger.bind()
- @bind()
@render()
@start: ->
new Courseware
- bind: ->
- $('.course-content .sequence, .course-content .tab')
- .bind 'contentChanged', @render
-
render: ->
- $('.course-content .video').each ->
- id = $(this).attr('id').replace(/video_/, '')
- new Video id, $(this).data('streams')
- $('.course-content .problems-wrapper').each ->
- id = $(this).data('problem-id')
- new Problem id, $(this).attr('id'), $(this).data('url')
+ XModule.loadModules('display')
$('.course-content .histogram').each ->
id = $(this).attr('id').replace(/histogram_/, '')
new Histogram id, $(this).data('histogram')
diff --git a/lms/static/coffee/src/main.coffee b/lms/static/coffee/src/main.coffee
index 06a12e5b6f..d74035cd2a 100644
--- a/lms/static/coffee/src/main.coffee
+++ b/lms/static/coffee/src/main.coffee
@@ -1,8 +1,4 @@
-jQuery.postWithPrefix = (url, data, callback, type) ->
- $.post("#{Courseware.prefix}#{url}", data, callback, type)
-
-jQuery.getWithPrefix = (url, data, callback, type) ->
- $.get("#{Courseware.prefix}#{url}", data, callback, type)
+AjaxPrefix.addAjaxPrefix(jQuery, -> Courseware.prefix)
$ ->
$.ajaxSetup
diff --git a/test_root/data/custom_tags/.git-keep b/lms/static/coffee/src/views/forgot_password.coffee
similarity index 100%
rename from test_root/data/custom_tags/.git-keep
rename to lms/static/coffee/src/views/forgot_password.coffee
diff --git a/lms/static/images/about_1.jpg b/lms/static/images/about_1.jpg
index c21f7ee836..a67cafe344 100644
Binary files a/lms/static/images/about_1.jpg and b/lms/static/images/about_1.jpg differ
diff --git a/lms/static/images/amplifier-slider-handle.png b/lms/static/images/amplifier-slider-handle.png
index 47e3c8d449..1a00b40255 100644
Binary files a/lms/static/images/amplifier-slider-handle.png and b/lms/static/images/amplifier-slider-handle.png differ
diff --git a/lms/static/images/anant.jpg b/lms/static/images/anant.jpg
deleted file mode 100644
index 900038d394..0000000000
Binary files a/lms/static/images/anant.jpg and /dev/null differ
diff --git a/lms/static/images/askbot/comment-vote-up.png b/lms/static/images/askbot/comment-vote-up.png
index 0ce3dcc9e7..05dc84e12e 100644
Binary files a/lms/static/images/askbot/comment-vote-up.png and b/lms/static/images/askbot/comment-vote-up.png differ
diff --git a/lms/static/images/askbot/search-icon.png b/lms/static/images/askbot/search-icon.png
index a6d4561b75..b06aa1163d 100644
Binary files a/lms/static/images/askbot/search-icon.png and b/lms/static/images/askbot/search-icon.png differ
diff --git a/lms/static/images/askbot/vote-arrow-down-activate.png b/lms/static/images/askbot/vote-arrow-down-activate.png
index 8b0ffc44d0..354c49dca6 100644
Binary files a/lms/static/images/askbot/vote-arrow-down-activate.png and b/lms/static/images/askbot/vote-arrow-down-activate.png differ
diff --git a/lms/static/images/askbot/vote-arrow-down.png b/lms/static/images/askbot/vote-arrow-down.png
index aace02ffb2..e67524077a 100644
Binary files a/lms/static/images/askbot/vote-arrow-down.png and b/lms/static/images/askbot/vote-arrow-down.png differ
diff --git a/lms/static/images/askbot/vote-arrow-up-activate.png b/lms/static/images/askbot/vote-arrow-up-activate.png
index 66aa8c9772..aa411c70e1 100644
Binary files a/lms/static/images/askbot/vote-arrow-up-activate.png and b/lms/static/images/askbot/vote-arrow-up-activate.png differ
diff --git a/lms/static/images/askbot/vote-arrow-up.png b/lms/static/images/askbot/vote-arrow-up.png
index e1920c652e..a35946cc51 100644
Binary files a/lms/static/images/askbot/vote-arrow-up.png and b/lms/static/images/askbot/vote-arrow-up.png differ
diff --git a/lms/static/images/askbot/wmd-buttons.png b/lms/static/images/askbot/wmd-buttons.png
index 3013a4ad02..1092100710 100755
Binary files a/lms/static/images/askbot/wmd-buttons.png and b/lms/static/images/askbot/wmd-buttons.png differ
diff --git a/lms/static/images/berkeley.png b/lms/static/images/berkeley.png
deleted file mode 100644
index 01dde485fa..0000000000
Binary files a/lms/static/images/berkeley.png and /dev/null differ
diff --git a/lms/static/images/berkeley_bw.png b/lms/static/images/berkeley_bw.png
deleted file mode 100644
index b524b581dd..0000000000
Binary files a/lms/static/images/berkeley_bw.png and /dev/null differ
diff --git a/lms/static/images/brazilstory_blog_arthur.jpg b/lms/static/images/brazilstory_blog_arthur.jpg
index f207bbbf7e..58efd75542 100644
Binary files a/lms/static/images/brazilstory_blog_arthur.jpg and b/lms/static/images/brazilstory_blog_arthur.jpg differ
diff --git a/lms/static/images/bullet-triangle.png b/lms/static/images/bullet-triangle.png
index cca2bc2ae1..d9d36f5a74 100644
Binary files a/lms/static/images/bullet-triangle.png and b/lms/static/images/bullet-triangle.png differ
diff --git a/lms/static/images/calc-icon.png b/lms/static/images/calc-icon.png
index a37423dff1..7be00daff0 100644
Binary files a/lms/static/images/calc-icon.png and b/lms/static/images/calc-icon.png differ
diff --git a/lms/static/images/contact-page.jpg b/lms/static/images/contact-page.jpg
new file mode 100644
index 0000000000..8b9e9a9bfa
Binary files /dev/null and b/lms/static/images/contact-page.jpg differ
diff --git a/lms/static/images/courses/edx_hp_video_thumbsmall.jpg b/lms/static/images/courses/edx_hp_video_thumbsmall.jpg
new file mode 100644
index 0000000000..644fa8e065
Binary files /dev/null and b/lms/static/images/courses/edx_hp_video_thumbsmall.jpg differ
diff --git a/lms/static/images/courses/history.png b/lms/static/images/courses/history.png
index a04d242d6a..c9dc8ada49 100644
Binary files a/lms/static/images/courses/history.png and b/lms/static/images/courses/history.png differ
diff --git a/lms/static/images/courses/math.png b/lms/static/images/courses/math.png
index de3328195a..2652b15473 100644
Binary files a/lms/static/images/courses/math.png and b/lms/static/images/courses/math.png differ
diff --git a/lms/static/images/courses/python.png b/lms/static/images/courses/python.png
deleted file mode 100644
index 05e7d6006a..0000000000
Binary files a/lms/static/images/courses/python.png and /dev/null differ
diff --git a/lms/static/images/courses/space1.jpg b/lms/static/images/courses/space1.jpg
deleted file mode 100644
index a7fde7dc4a..0000000000
Binary files a/lms/static/images/courses/space1.jpg and /dev/null differ
diff --git a/lms/static/images/courses/space2.jpg.REMOVED.git-id b/lms/static/images/courses/space2.jpg.REMOVED.git-id
deleted file mode 100644
index d6ffb516a8..0000000000
--- a/lms/static/images/courses/space2.jpg.REMOVED.git-id
+++ /dev/null
@@ -1 +0,0 @@
-69017eed8a9ec5b9caf28814cadf01ba69b4b3bc
\ No newline at end of file
diff --git a/lms/static/images/courses/space3.jpg.REMOVED.git-id b/lms/static/images/courses/space3.jpg.REMOVED.git-id
deleted file mode 100644
index e037dd6c43..0000000000
--- a/lms/static/images/courses/space3.jpg.REMOVED.git-id
+++ /dev/null
@@ -1 +0,0 @@
-74bca798766449c9f2c87244410ca8fa30d5482e
\ No newline at end of file
diff --git a/lms/static/images/courses/space4.jpg.REMOVED.git-id b/lms/static/images/courses/space4.jpg.REMOVED.git-id
deleted file mode 100644
index 78001082eb..0000000000
--- a/lms/static/images/courses/space4.jpg.REMOVED.git-id
+++ /dev/null
@@ -1 +0,0 @@
-184bae027b6853b575b8de2329f77089bcc87966
\ No newline at end of file
diff --git a/lms/static/images/courses/space5.jpg b/lms/static/images/courses/space5.jpg
deleted file mode 100644
index 65dad74b01..0000000000
Binary files a/lms/static/images/courses/space5.jpg and /dev/null differ
diff --git a/lms/static/images/courses/video-thumb.jpg b/lms/static/images/courses/video-thumb.jpg
new file mode 100644
index 0000000000..abda2d5b66
Binary files /dev/null and b/lms/static/images/courses/video-thumb.jpg differ
diff --git a/lms/static/images/document-download.png b/lms/static/images/document-download.png
index 9cceef2486..767bf8029e 100644
Binary files a/lms/static/images/document-download.png and b/lms/static/images/document-download.png differ
diff --git a/lms/static/images/edx-location.png b/lms/static/images/edx-location.png
index 8e12854c10..20f1ec18bc 100644
Binary files a/lms/static/images/edx-location.png and b/lms/static/images/edx-location.png differ
diff --git a/lms/static/images/edx-logo-large-bw.png b/lms/static/images/edx-logo-large-bw.png
new file mode 100644
index 0000000000..d888e1f4bb
Binary files /dev/null and b/lms/static/images/edx-logo-large-bw.png differ
diff --git a/lms/static/images/edx-logo-large-color.png b/lms/static/images/edx-logo-large-color.png
new file mode 100644
index 0000000000..f2cffea7a7
Binary files /dev/null and b/lms/static/images/edx-logo-large-color.png differ
diff --git a/lms/static/images/edx.png b/lms/static/images/edx.png
index e5bb6e4cc6..22ed9b92c3 100644
Binary files a/lms/static/images/edx.png and b/lms/static/images/edx.png differ
diff --git a/lms/static/images/edx_bw.png b/lms/static/images/edx_bw.png
index 94a924ea43..25e4725262 100644
Binary files a/lms/static/images/edx_bw.png and b/lms/static/images/edx_bw.png differ
diff --git a/lms/static/images/favicon.ico b/lms/static/images/favicon.ico
new file mode 100644
index 0000000000..13801059f1
Binary files /dev/null and b/lms/static/images/favicon.ico differ
diff --git a/lms/static/images/fullscreen.png b/lms/static/images/fullscreen.png
deleted file mode 100644
index e2f9054fe1..0000000000
Binary files a/lms/static/images/fullscreen.png and /dev/null differ
diff --git a/lms/static/images/gerald.jpg b/lms/static/images/gerald.jpg
deleted file mode 100644
index 4863d30b9e..0000000000
Binary files a/lms/static/images/gerald.jpg and /dev/null differ
diff --git a/lms/static/images/harvard.png b/lms/static/images/harvard.png
deleted file mode 100644
index ecfef5245d..0000000000
Binary files a/lms/static/images/harvard.png and /dev/null differ
diff --git a/lms/static/images/harvard_bw.png b/lms/static/images/harvard_bw.png
deleted file mode 100644
index e65ca2ae55..0000000000
Binary files a/lms/static/images/harvard_bw.png and /dev/null differ
diff --git a/lms/static/images/harvard_cropped.png b/lms/static/images/harvard_cropped.png
deleted file mode 100644
index e72aeb0a20..0000000000
Binary files a/lms/static/images/harvard_cropped.png and /dev/null differ
diff --git a/lms/static/images/header-logo.png b/lms/static/images/header-logo.png
index c851a703e7..f1d2357e6b 100644
Binary files a/lms/static/images/header-logo.png and b/lms/static/images/header-logo.png differ
diff --git a/lms/static/images/home_bg.jpg b/lms/static/images/home_bg.jpg
index 7f86a9d7bd..6df3ab2f81 100644
Binary files a/lms/static/images/home_bg.jpg and b/lms/static/images/home_bg.jpg differ
diff --git a/lms/static/images/homepage-bg.jpg b/lms/static/images/homepage-bg.jpg
new file mode 100644
index 0000000000..61da5545c1
Binary files /dev/null and b/lms/static/images/homepage-bg.jpg differ
diff --git a/lms/static/images/homepage_interns_placeholder_2025x550.jpg b/lms/static/images/homepage_interns_placeholder_2025x550.jpg
new file mode 100644
index 0000000000..d5d9b915da
Binary files /dev/null and b/lms/static/images/homepage_interns_placeholder_2025x550.jpg differ
diff --git a/lms/static/images/intro-video.jpg b/lms/static/images/intro-video.jpg
new file mode 100644
index 0000000000..45e309e7a4
Binary files /dev/null and b/lms/static/images/intro-video.jpg differ
diff --git a/lms/static/images/jobs.jpeg b/lms/static/images/jobs.jpeg
new file mode 100644
index 0000000000..af2424f154
Binary files /dev/null and b/lms/static/images/jobs.jpeg differ
diff --git a/lms/static/images/laffont_temp.jpg b/lms/static/images/laffont_temp.jpg
deleted file mode 100644
index caaedc844f..0000000000
Binary files a/lms/static/images/laffont_temp.jpg and /dev/null differ
diff --git a/lms/static/images/logo.png b/lms/static/images/logo.png
index b9e4199c85..0ba6a4e175 100644
Binary files a/lms/static/images/logo.png and b/lms/static/images/logo.png differ
diff --git a/lms/static/images/logo_bw.png b/lms/static/images/logo_bw.png
index 6ce9c460db..236e3fd0c2 100644
Binary files a/lms/static/images/logo_bw.png and b/lms/static/images/logo_bw.png differ
diff --git a/lms/static/images/logo_harvard_v5.png b/lms/static/images/logo_harvard_v5.png
index aa1f8604cb..0c8f016798 100644
Binary files a/lms/static/images/logo_harvard_v5.png and b/lms/static/images/logo_harvard_v5.png differ
diff --git a/lms/static/images/mit.png b/lms/static/images/mit.png
deleted file mode 100644
index 0a673cfd11..0000000000
Binary files a/lms/static/images/mit.png and /dev/null differ
diff --git a/lms/static/images/mit_bw.png b/lms/static/images/mit_bw.png
deleted file mode 100644
index 4c50c51b9c..0000000000
Binary files a/lms/static/images/mit_bw.png and /dev/null differ
diff --git a/lms/static/images/mongolia_post.jpeg b/lms/static/images/mongolia_post.jpeg
index bfccf0c105..48949d14ee 100644
Binary files a/lms/static/images/mongolia_post.jpeg and b/lms/static/images/mongolia_post.jpeg differ
diff --git a/lms/static/images/open-arrow.png b/lms/static/images/open-arrow.png
deleted file mode 100644
index 4bedb61081..0000000000
Binary files a/lms/static/images/open-arrow.png and /dev/null differ
diff --git a/lms/static/images/pause-icon.png b/lms/static/images/pause-icon.png
index e3eac51947..b24946d5cf 100644
Binary files a/lms/static/images/pause-icon.png and b/lms/static/images/pause-icon.png differ
diff --git a/lms/static/images/piotr.jpg b/lms/static/images/piotr.jpg
deleted file mode 100644
index ff63f6f9fd..0000000000
Binary files a/lms/static/images/piotr.jpg and /dev/null differ
diff --git a/lms/static/images/play-icon.png b/lms/static/images/play-icon.png
index 2888b1c271..4927729492 100644
Binary files a/lms/static/images/play-icon.png and b/lms/static/images/play-icon.png differ
diff --git a/lms/static/images/portal-icons/calendar-icon.png b/lms/static/images/portal-icons/calendar-icon.png
index 4d21e405c8..ec9d4bfc0d 100644
Binary files a/lms/static/images/portal-icons/calendar-icon.png and b/lms/static/images/portal-icons/calendar-icon.png differ
diff --git a/lms/static/images/portal-icons/chart-icon.png b/lms/static/images/portal-icons/chart-icon.png
index fc6e224f59..c5f364bcb5 100644
Binary files a/lms/static/images/portal-icons/chart-icon.png and b/lms/static/images/portal-icons/chart-icon.png differ
diff --git a/lms/static/images/portal-icons/course-info-icon.png b/lms/static/images/portal-icons/course-info-icon.png
index 178a582cbd..36fd0c0773 100644
Binary files a/lms/static/images/portal-icons/course-info-icon.png and b/lms/static/images/portal-icons/course-info-icon.png differ
diff --git a/lms/static/images/portal-icons/gear-icon.png b/lms/static/images/portal-icons/gear-icon.png
index 4e66eb3e3e..039ce282bb 100644
Binary files a/lms/static/images/portal-icons/gear-icon.png and b/lms/static/images/portal-icons/gear-icon.png differ
diff --git a/lms/static/images/portal-icons/home-icon.png b/lms/static/images/portal-icons/home-icon.png
index 7022487bf4..ec87b671ee 100644
Binary files a/lms/static/images/portal-icons/home-icon.png and b/lms/static/images/portal-icons/home-icon.png differ
diff --git a/lms/static/images/portal-icons/language-icon.png b/lms/static/images/portal-icons/language-icon.png
index 9ab8a178a6..e83539d0d6 100644
Binary files a/lms/static/images/portal-icons/language-icon.png and b/lms/static/images/portal-icons/language-icon.png differ
diff --git a/lms/static/images/portal-icons/pencil-icon.png b/lms/static/images/portal-icons/pencil-icon.png
index 30c99c191f..8f8715c255 100644
Binary files a/lms/static/images/portal-icons/pencil-icon.png and b/lms/static/images/portal-icons/pencil-icon.png differ
diff --git a/lms/static/images/portal-icons/prerec-icon.png b/lms/static/images/portal-icons/prerec-icon.png
new file mode 100644
index 0000000000..78f0eb48f1
Binary files /dev/null and b/lms/static/images/portal-icons/prerec-icon.png differ
diff --git a/lms/static/images/portal-icons/video-play-icon.png b/lms/static/images/portal-icons/video-play-icon.png
index b55612c1a0..6fea9eed9b 100644
Binary files a/lms/static/images/portal-icons/video-play-icon.png and b/lms/static/images/portal-icons/video-play-icon.png differ
diff --git a/lms/static/images/press/bbc_logo_178x138.jpeg b/lms/static/images/press/bbc_logo_178x138.jpeg
new file mode 100644
index 0000000000..e3395e56a9
Binary files /dev/null and b/lms/static/images/press/bbc_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/bloomberg_logo_178x138.jpeg b/lms/static/images/press/bloomberg_logo_178x138.jpeg
new file mode 100644
index 0000000000..d87819b7cd
Binary files /dev/null and b/lms/static/images/press/bloomberg_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/bostonglobe_logo_178x138.jpeg b/lms/static/images/press/bostonglobe_logo_178x138.jpeg
new file mode 100644
index 0000000000..0224476b29
Binary files /dev/null and b/lms/static/images/press/bostonglobe_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/chroniclehighered_logo_178x138.jpeg b/lms/static/images/press/chroniclehighered_logo_178x138.jpeg
new file mode 100644
index 0000000000..95dce54e2e
Binary files /dev/null and b/lms/static/images/press/chroniclehighered_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/edx_logo_108x81.jpeg b/lms/static/images/press/edx_logo_108x81.jpeg
new file mode 100644
index 0000000000..0660d9de08
Binary files /dev/null and b/lms/static/images/press/edx_logo_108x81.jpeg differ
diff --git a/lms/static/images/press/gigaom_logo_178x138.jpeg b/lms/static/images/press/gigaom_logo_178x138.jpeg
new file mode 100644
index 0000000000..cdbda2f5d1
Binary files /dev/null and b/lms/static/images/press/gigaom_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/harvardcrimson_logo_178x138.jpeg b/lms/static/images/press/harvardcrimson_logo_178x138.jpeg
new file mode 100644
index 0000000000..7fa3bef5e1
Binary files /dev/null and b/lms/static/images/press/harvardcrimson_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/harvardgazette_logo_178x138.jpeg b/lms/static/images/press/harvardgazette_logo_178x138.jpeg
new file mode 100644
index 0000000000..1f0bfac70b
Binary files /dev/null and b/lms/static/images/press/harvardgazette_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/harvardmagazine_logo_178x138.jpeg b/lms/static/images/press/harvardmagazine_logo_178x138.jpeg
new file mode 100644
index 0000000000..58b29a5596
Binary files /dev/null and b/lms/static/images/press/harvardmagazine_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/harvarduniv_logo_178x138.jpeg b/lms/static/images/press/harvarduniv_logo_178x138.jpeg
new file mode 100644
index 0000000000..66dd05fb88
Binary files /dev/null and b/lms/static/images/press/harvarduniv_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/huffpost_logo_178x138.jpeg b/lms/static/images/press/huffpost_logo_178x138.jpeg
new file mode 100644
index 0000000000..ed30f223f2
Binary files /dev/null and b/lms/static/images/press/huffpost_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/ieee_logo_178x138.jpeg b/lms/static/images/press/ieee_logo_178x138.jpeg
new file mode 100644
index 0000000000..6f2d5f7bfa
Binary files /dev/null and b/lms/static/images/press/ieee_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/latimes_logo_178x138.jpeg b/lms/static/images/press/latimes_logo_178x138.jpeg
new file mode 100644
index 0000000000..fed922f846
Binary files /dev/null and b/lms/static/images/press/latimes_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/mercurynews_logo_178x138.jpeg b/lms/static/images/press/mercurynews_logo_178x138.jpeg
new file mode 100644
index 0000000000..798749ab22
Binary files /dev/null and b/lms/static/images/press/mercurynews_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/mit_logo_178x138.jpeg b/lms/static/images/press/mit_logo_178x138.jpeg
new file mode 100644
index 0000000000..3fb78ac90c
Binary files /dev/null and b/lms/static/images/press/mit_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/npr_logo_178x138.jpeg b/lms/static/images/press/npr_logo_178x138.jpeg
new file mode 100644
index 0000000000..987f57206c
Binary files /dev/null and b/lms/static/images/press/npr_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/nyt_logo_178x138.jpeg b/lms/static/images/press/nyt_logo_178x138.jpeg
new file mode 100644
index 0000000000..b40af3e79f
Binary files /dev/null and b/lms/static/images/press/nyt_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/oef_logo_178x138.jpeg b/lms/static/images/press/oef_logo_178x138.jpeg
new file mode 100644
index 0000000000..b4469e7bb8
Binary files /dev/null and b/lms/static/images/press/oef_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/tumblr_tumbnail_brazilian_teen.jpg b/lms/static/images/press/tumblr_tumbnail_brazilian_teen.jpg
new file mode 100644
index 0000000000..ea88df6230
Binary files /dev/null and b/lms/static/images/press/tumblr_tumbnail_brazilian_teen.jpg differ
diff --git a/lms/static/images/press/tumblr_tumbnail_opening_doors_mongolia.jpg b/lms/static/images/press/tumblr_tumbnail_opening_doors_mongolia.jpg
new file mode 100644
index 0000000000..e1cd20b9df
Binary files /dev/null and b/lms/static/images/press/tumblr_tumbnail_opening_doors_mongolia.jpg differ
diff --git a/lms/static/images/press/univworldnews_logo_178x138.jpeg b/lms/static/images/press/univworldnews_logo_178x138.jpeg
new file mode 100644
index 0000000000..038475c4e0
Binary files /dev/null and b/lms/static/images/press/univworldnews_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/usa_logo_178x138.jpeg b/lms/static/images/press/usa_logo_178x138.jpeg
new file mode 100644
index 0000000000..ed3a1c50e6
Binary files /dev/null and b/lms/static/images/press/usa_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/usnews_logo_178x138.jpeg b/lms/static/images/press/usnews_logo_178x138.jpeg
new file mode 100644
index 0000000000..55c7cb51e3
Binary files /dev/null and b/lms/static/images/press/usnews_logo_178x138.jpeg differ
diff --git a/lms/static/images/press/wbur_logo_178x138.jpeg b/lms/static/images/press/wbur_logo_178x138.jpeg
new file mode 100644
index 0000000000..2319d82140
Binary files /dev/null and b/lms/static/images/press/wbur_logo_178x138.jpeg differ
diff --git a/lms/static/images/profile.jpg b/lms/static/images/profile.jpg
index 79633c5e15..306f80ec25 100644
Binary files a/lms/static/images/profile.jpg and b/lms/static/images/profile.jpg differ
diff --git a/lms/static/images/schools.png b/lms/static/images/schools.png
index c1c668de60..c41cac6eed 100644
Binary files a/lms/static/images/schools.png and b/lms/static/images/schools.png differ
diff --git a/lms/static/images/sequence-nav/document-icon-current.png b/lms/static/images/sequence-nav/document-icon-current.png
deleted file mode 100644
index 52f9185e93..0000000000
Binary files a/lms/static/images/sequence-nav/document-icon-current.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/document-icon-normal.png b/lms/static/images/sequence-nav/document-icon-normal.png
deleted file mode 100644
index 6e76ad0d93..0000000000
Binary files a/lms/static/images/sequence-nav/document-icon-normal.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/document-icon-visited.png b/lms/static/images/sequence-nav/document-icon-visited.png
deleted file mode 100644
index 5fe36cbbee..0000000000
Binary files a/lms/static/images/sequence-nav/document-icon-visited.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/edit.png b/lms/static/images/sequence-nav/edit.png
deleted file mode 100644
index 9ad3945c35..0000000000
Binary files a/lms/static/images/sequence-nav/edit.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/history.png b/lms/static/images/sequence-nav/history.png
deleted file mode 100644
index fa8a2d8177..0000000000
Binary files a/lms/static/images/sequence-nav/history.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/list-icon-current.png b/lms/static/images/sequence-nav/list-icon-current.png
deleted file mode 100644
index d8ad94d53e..0000000000
Binary files a/lms/static/images/sequence-nav/list-icon-current.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/list-icon-visited.png b/lms/static/images/sequence-nav/list-icon-visited.png
deleted file mode 100644
index 2df2935dbe..0000000000
Binary files a/lms/static/images/sequence-nav/list-icon-visited.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/next-icon.png b/lms/static/images/sequence-nav/next-icon.png
deleted file mode 100644
index 413ce38d1b..0000000000
Binary files a/lms/static/images/sequence-nav/next-icon.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/previous-icon.png b/lms/static/images/sequence-nav/previous-icon.png
deleted file mode 100644
index 0b3f369176..0000000000
Binary files a/lms/static/images/sequence-nav/previous-icon.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/status/dash.png b/lms/static/images/sequence-nav/status/dash.png
deleted file mode 100644
index 79e7fa8f7f..0000000000
Binary files a/lms/static/images/sequence-nav/status/dash.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/status/not-started.png b/lms/static/images/sequence-nav/status/not-started.png
deleted file mode 100644
index 826e28609b..0000000000
Binary files a/lms/static/images/sequence-nav/status/not-started.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/vertical-icon.png b/lms/static/images/sequence-nav/vertical-icon.png
deleted file mode 100644
index 6100b678c4..0000000000
Binary files a/lms/static/images/sequence-nav/vertical-icon.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/video-icon-visited.png b/lms/static/images/sequence-nav/video-icon-visited.png
deleted file mode 100644
index e09aad8d20..0000000000
Binary files a/lms/static/images/sequence-nav/video-icon-visited.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/video-icon.png b/lms/static/images/sequence-nav/video-icon.png
deleted file mode 100644
index e68ec0f8ae..0000000000
Binary files a/lms/static/images/sequence-nav/video-icon.png and /dev/null differ
diff --git a/lms/static/images/sequence-nav/view.png b/lms/static/images/sequence-nav/view.png
deleted file mode 100644
index 62f04aa993..0000000000
Binary files a/lms/static/images/sequence-nav/view.png and /dev/null differ
diff --git a/lms/static/images/shot-2-large.jpg b/lms/static/images/shot-2-large.jpg
deleted file mode 100644
index 592c327ec2..0000000000
Binary files a/lms/static/images/shot-2-large.jpg and /dev/null differ
diff --git a/lms/static/images/shot-3-large.jpg b/lms/static/images/shot-3-large.jpg
deleted file mode 100644
index 34e5134d9d..0000000000
Binary files a/lms/static/images/shot-3-large.jpg and /dev/null differ
diff --git a/lms/static/images/shot-4-large.jpg b/lms/static/images/shot-4-large.jpg
deleted file mode 100644
index af9550c702..0000000000
Binary files a/lms/static/images/shot-4-large.jpg and /dev/null differ
diff --git a/lms/static/images/slider-bars.png b/lms/static/images/slider-bars.png
index f2e014f4b7..e5b005344c 100644
Binary files a/lms/static/images/slider-bars.png and b/lms/static/images/slider-bars.png differ
diff --git a/lms/static/images/slider-handle.png b/lms/static/images/slider-handle.png
deleted file mode 100644
index 94ebe6f0bc..0000000000
Binary files a/lms/static/images/slider-handle.png and /dev/null differ
diff --git a/lms/static/images/social/email-sharing.png b/lms/static/images/social/email-sharing.png
new file mode 100644
index 0000000000..57fcee00e9
Binary files /dev/null and b/lms/static/images/social/email-sharing.png differ
diff --git a/lms/static/images/social/facebook-sharing.png b/lms/static/images/social/facebook-sharing.png
new file mode 100644
index 0000000000..a82612c342
Binary files /dev/null and b/lms/static/images/social/facebook-sharing.png differ
diff --git a/lms/static/images/social/google-plus-sharing.png b/lms/static/images/social/google-plus-sharing.png
new file mode 100644
index 0000000000..04f21d7860
Binary files /dev/null and b/lms/static/images/social/google-plus-sharing.png differ
diff --git a/lms/static/images/email-sharing.png b/lms/static/images/social/lrg/email-sharing.png
similarity index 100%
rename from lms/static/images/email-sharing.png
rename to lms/static/images/social/lrg/email-sharing.png
diff --git a/lms/static/images/facebook-sharing.png b/lms/static/images/social/lrg/facebook-sharing.png
similarity index 100%
rename from lms/static/images/facebook-sharing.png
rename to lms/static/images/social/lrg/facebook-sharing.png
diff --git a/lms/static/images/facebook.png b/lms/static/images/social/lrg/facebook.png
similarity index 100%
rename from lms/static/images/facebook.png
rename to lms/static/images/social/lrg/facebook.png
diff --git a/lms/static/images/social/lrg/google-plus-sharing.png b/lms/static/images/social/lrg/google-plus-sharing.png
new file mode 100644
index 0000000000..ddf2abef83
Binary files /dev/null and b/lms/static/images/social/lrg/google-plus-sharing.png differ
diff --git a/lms/static/images/linkedin.png b/lms/static/images/social/lrg/linkedin.png
similarity index 100%
rename from lms/static/images/linkedin.png
rename to lms/static/images/social/lrg/linkedin.png
diff --git a/lms/static/images/twitter-sharing.png b/lms/static/images/social/lrg/twitter-sharing.png
similarity index 100%
rename from lms/static/images/twitter-sharing.png
rename to lms/static/images/social/lrg/twitter-sharing.png
diff --git a/lms/static/images/twitter.png b/lms/static/images/social/lrg/twitter.png
similarity index 100%
rename from lms/static/images/twitter.png
rename to lms/static/images/social/lrg/twitter.png
diff --git a/lms/static/images/social/lrg/youtube-sharing.png b/lms/static/images/social/lrg/youtube-sharing.png
new file mode 100644
index 0000000000..111aa685a8
Binary files /dev/null and b/lms/static/images/social/lrg/youtube-sharing.png differ
diff --git a/lms/static/images/social/twitter-sharing.png b/lms/static/images/social/twitter-sharing.png
new file mode 100644
index 0000000000..7d171d0fef
Binary files /dev/null and b/lms/static/images/social/twitter-sharing.png differ
diff --git a/lms/static/images/social/youtube-sharing.png b/lms/static/images/social/youtube-sharing.png
new file mode 100644
index 0000000000..a26b18121b
Binary files /dev/null and b/lms/static/images/social/youtube-sharing.png differ
diff --git a/lms/static/images/ui-bg_flat_0_aaaaaa_40x100.png b/lms/static/images/ui-bg_flat_0_aaaaaa_40x100.png
index 5b5dab2ab7..e425e6e46e 100644
Binary files a/lms/static/images/ui-bg_flat_0_aaaaaa_40x100.png and b/lms/static/images/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/lms/static/images/ui-bg_flat_0_eeeeee_40x100.png b/lms/static/images/ui-bg_flat_0_eeeeee_40x100.png
index e44f861be1..f1cfaa5636 100644
Binary files a/lms/static/images/ui-bg_flat_0_eeeeee_40x100.png and b/lms/static/images/ui-bg_flat_0_eeeeee_40x100.png differ
diff --git a/lms/static/images/ui-bg_flat_55_ffffff_40x100.png b/lms/static/images/ui-bg_flat_55_ffffff_40x100.png
index ac8b229af9..72d4757363 100644
Binary files a/lms/static/images/ui-bg_flat_55_ffffff_40x100.png and b/lms/static/images/ui-bg_flat_55_ffffff_40x100.png differ
diff --git a/lms/static/images/ui-bg_flat_75_ffffff_40x100.png b/lms/static/images/ui-bg_flat_75_ffffff_40x100.png
index ac8b229af9..72d4757363 100644
Binary files a/lms/static/images/ui-bg_flat_75_ffffff_40x100.png and b/lms/static/images/ui-bg_flat_75_ffffff_40x100.png differ
diff --git a/lms/static/images/ui-bg_glass_65_023063_1x400.png b/lms/static/images/ui-bg_glass_65_023063_1x400.png
index 644a1f78e3..cb030d12ee 100644
Binary files a/lms/static/images/ui-bg_glass_65_023063_1x400.png and b/lms/static/images/ui-bg_glass_65_023063_1x400.png differ
diff --git a/lms/static/images/ui-bg_glass_65_ffffff_1x400.png b/lms/static/images/ui-bg_glass_65_ffffff_1x400.png
index 42ccba269b..e7d01fac6a 100644
Binary files a/lms/static/images/ui-bg_glass_65_ffffff_1x400.png and b/lms/static/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/lms/static/images/ui-bg_highlight-soft_100_7fbcdf_1x100.png b/lms/static/images/ui-bg_highlight-soft_100_7fbcdf_1x100.png
index 6c372415be..0c1ee5c9cd 100644
Binary files a/lms/static/images/ui-bg_highlight-soft_100_7fbcdf_1x100.png and b/lms/static/images/ui-bg_highlight-soft_100_7fbcdf_1x100.png differ
diff --git a/lms/static/images/ui-bg_highlight-soft_100_bddeff_1x100.png b/lms/static/images/ui-bg_highlight-soft_100_bddeff_1x100.png
index 2f6196c9b3..41d7a7d7a7 100644
Binary files a/lms/static/images/ui-bg_highlight-soft_100_bddeff_1x100.png and b/lms/static/images/ui-bg_highlight-soft_100_bddeff_1x100.png differ
diff --git a/lms/static/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png b/lms/static/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png
index 5dcfaa9a01..20bb28e15b 100644
Binary files a/lms/static/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png and b/lms/static/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png differ
diff --git a/lms/static/images/ui-bg_highlight-soft_25_7fbcdf_1x100.png b/lms/static/images/ui-bg_highlight-soft_25_7fbcdf_1x100.png
index 994b7557c4..903760d875 100644
Binary files a/lms/static/images/ui-bg_highlight-soft_25_7fbcdf_1x100.png and b/lms/static/images/ui-bg_highlight-soft_25_7fbcdf_1x100.png differ
diff --git a/lms/static/images/ui-bg_highlight-soft_25_bddeff_1x100.png b/lms/static/images/ui-bg_highlight-soft_25_bddeff_1x100.png
index 77f2cf7bc2..a4c65deaef 100644
Binary files a/lms/static/images/ui-bg_highlight-soft_25_bddeff_1x100.png and b/lms/static/images/ui-bg_highlight-soft_25_bddeff_1x100.png differ
diff --git a/lms/static/images/ui-bg_highlight-soft_50_7fbcfd_1x100.png b/lms/static/images/ui-bg_highlight-soft_50_7fbcfd_1x100.png
index 6f70ca4003..8742d91098 100644
Binary files a/lms/static/images/ui-bg_highlight-soft_50_7fbcfd_1x100.png and b/lms/static/images/ui-bg_highlight-soft_50_7fbcfd_1x100.png differ
diff --git a/lms/static/images/ui-icons_031634_256x240.png b/lms/static/images/ui-icons_031634_256x240.png
index 09c70d6470..1a38b25a5c 100644
Binary files a/lms/static/images/ui-icons_031634_256x240.png and b/lms/static/images/ui-icons_031634_256x240.png differ
diff --git a/lms/static/images/ui-icons_454545_256x240.png b/lms/static/images/ui-icons_454545_256x240.png
index 59bd45b907..c7d8883560 100644
Binary files a/lms/static/images/ui-icons_454545_256x240.png and b/lms/static/images/ui-icons_454545_256x240.png differ
diff --git a/lms/static/images/ui-icons_adcc80_256x240.png b/lms/static/images/ui-icons_adcc80_256x240.png
index 370191a5ea..28e0801ee2 100644
Binary files a/lms/static/images/ui-icons_adcc80_256x240.png and b/lms/static/images/ui-icons_adcc80_256x240.png differ
diff --git a/lms/static/images/ui-icons_fa720a_256x240.png b/lms/static/images/ui-icons_fa720a_256x240.png
index b70cbd1ab7..ac5171d3ae 100644
Binary files a/lms/static/images/ui-icons_fa720a_256x240.png and b/lms/static/images/ui-icons_fa720a_256x240.png differ
diff --git a/lms/static/images/ui-icons_ffffff_256x240.png b/lms/static/images/ui-icons_ffffff_256x240.png
index 42f8f992c7..46df091ffb 100644
Binary files a/lms/static/images/ui-icons_ffffff_256x240.png and b/lms/static/images/ui-icons_ffffff_256x240.png differ
diff --git a/lms/static/images/unanswered-icon.png b/lms/static/images/unanswered-icon.png
index 8ea9669b8c..ff88b00696 100644
Binary files a/lms/static/images/unanswered-icon.png and b/lms/static/images/unanswered-icon.png differ
diff --git a/lms/static/images/university/berkeley/berkeley-cover.jpeg b/lms/static/images/university/berkeley/berkeley-cover.jpeg
new file mode 100644
index 0000000000..16c807555d
Binary files /dev/null and b/lms/static/images/university/berkeley/berkeley-cover.jpeg differ
diff --git a/lms/static/images/university/berkeley/berkeley.png b/lms/static/images/university/berkeley/berkeley.png
new file mode 100644
index 0000000000..ca85266538
Binary files /dev/null and b/lms/static/images/university/berkeley/berkeley.png differ
diff --git a/lms/static/images/university/berkeley/berkeley_bw.png b/lms/static/images/university/berkeley/berkeley_bw.png
new file mode 100644
index 0000000000..9904e61315
Binary files /dev/null and b/lms/static/images/university/berkeley/berkeley_bw.png differ
diff --git a/lms/static/images/university/berkeley/berkeley_fountain_2025x550.jpg b/lms/static/images/university/berkeley/berkeley_fountain_2025x550.jpg
new file mode 100644
index 0000000000..16c807555d
Binary files /dev/null and b/lms/static/images/university/berkeley/berkeley_fountain_2025x550.jpg differ
diff --git a/lms/static/images/university/harvard/about-harvard.jpg b/lms/static/images/university/harvard/about-harvard.jpg
new file mode 100644
index 0000000000..1e348faaa5
Binary files /dev/null and b/lms/static/images/university/harvard/about-harvard.jpg differ
diff --git a/lms/static/images/university/harvard/about_harvard_page_2025x550.jpg b/lms/static/images/university/harvard/about_harvard_page_2025x550.jpg
new file mode 100644
index 0000000000..342543ef26
Binary files /dev/null and b/lms/static/images/university/harvard/about_harvard_page_2025x550.jpg differ
diff --git a/lms/static/images/university/harvard/harvard-cover.jpeg b/lms/static/images/university/harvard/harvard-cover.jpeg
new file mode 100644
index 0000000000..2c29dd99ee
Binary files /dev/null and b/lms/static/images/university/harvard/harvard-cover.jpeg differ
diff --git a/lms/static/images/university/harvard/harvard.png b/lms/static/images/university/harvard/harvard.png
new file mode 100644
index 0000000000..cee03aeeba
Binary files /dev/null and b/lms/static/images/university/harvard/harvard.png differ
diff --git a/lms/static/images/university/harvard/harvard_bw.png b/lms/static/images/university/harvard/harvard_bw.png
new file mode 100644
index 0000000000..5d1afefb84
Binary files /dev/null and b/lms/static/images/university/harvard/harvard_bw.png differ
diff --git a/lms/static/images/university/harvard/harvard_cropped.png b/lms/static/images/university/harvard/harvard_cropped.png
new file mode 100644
index 0000000000..e76b24fd59
Binary files /dev/null and b/lms/static/images/university/harvard/harvard_cropped.png differ
diff --git a/lms/static/images/university/mit/about-mit.jpg b/lms/static/images/university/mit/about-mit.jpg
new file mode 100644
index 0000000000..f2051eeb9b
Binary files /dev/null and b/lms/static/images/university/mit/about-mit.jpg differ
diff --git a/lms/static/images/university/mit/mit.png b/lms/static/images/university/mit/mit.png
new file mode 100644
index 0000000000..8be5c5f2b6
Binary files /dev/null and b/lms/static/images/university/mit/mit.png differ
diff --git a/lms/static/images/university/mit/mit_bw.png b/lms/static/images/university/mit/mit_bw.png
new file mode 100644
index 0000000000..121683acd9
Binary files /dev/null and b/lms/static/images/university/mit/mit_bw.png differ
diff --git a/lms/static/images/university/mit/shot-2-large.jpg b/lms/static/images/university/mit/shot-2-large.jpg
new file mode 100644
index 0000000000..09541dc372
Binary files /dev/null and b/lms/static/images/university/mit/shot-2-large.jpg differ
diff --git a/lms/static/images/university/mit/shot-3-large.jpg b/lms/static/images/university/mit/shot-3-large.jpg
new file mode 100644
index 0000000000..327073f937
Binary files /dev/null and b/lms/static/images/university/mit/shot-3-large.jpg differ
diff --git a/lms/static/images/university/mit/shot-4-large.jpg b/lms/static/images/university/mit/shot-4-large.jpg
new file mode 100644
index 0000000000..7c488bfccf
Binary files /dev/null and b/lms/static/images/university/mit/shot-4-large.jpg differ
diff --git a/lms/static/images/shot-5-large.jpg b/lms/static/images/university/mit/shot-5-large.jpg
similarity index 100%
rename from lms/static/images/shot-5-large.jpg
rename to lms/static/images/university/mit/shot-5-large.jpg
diff --git a/lms/static/images/vcr.png b/lms/static/images/vcr.png
deleted file mode 100644
index aa2ac99e47..0000000000
Binary files a/lms/static/images/vcr.png and /dev/null differ
diff --git a/lms/static/images/video-image.png b/lms/static/images/video-image.png
index 379403a4c5..6963d04540 100644
Binary files a/lms/static/images/video-image.png and b/lms/static/images/video-image.png differ
diff --git a/lms/static/images_old/sequence-nav/document-icon-current.png b/lms/static/images_old/sequence-nav/document-icon-current.png
deleted file mode 100644
index 52f9185e93..0000000000
Binary files a/lms/static/images_old/sequence-nav/document-icon-current.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/document-icon-normal.png b/lms/static/images_old/sequence-nav/document-icon-normal.png
deleted file mode 100644
index 6e76ad0d93..0000000000
Binary files a/lms/static/images_old/sequence-nav/document-icon-normal.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/document-icon-visited.png b/lms/static/images_old/sequence-nav/document-icon-visited.png
deleted file mode 100644
index 5fe36cbbee..0000000000
Binary files a/lms/static/images_old/sequence-nav/document-icon-visited.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/edit.png b/lms/static/images_old/sequence-nav/edit.png
deleted file mode 100644
index 9ad3945c35..0000000000
Binary files a/lms/static/images_old/sequence-nav/edit.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/history.png b/lms/static/images_old/sequence-nav/history.png
deleted file mode 100644
index fa8a2d8177..0000000000
Binary files a/lms/static/images_old/sequence-nav/history.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/list-icon-current.png b/lms/static/images_old/sequence-nav/list-icon-current.png
deleted file mode 100644
index d8ad94d53e..0000000000
Binary files a/lms/static/images_old/sequence-nav/list-icon-current.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/list-icon-normal.png b/lms/static/images_old/sequence-nav/list-icon-normal.png
deleted file mode 100644
index 42c473e82b..0000000000
Binary files a/lms/static/images_old/sequence-nav/list-icon-normal.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/list-icon-visited.png b/lms/static/images_old/sequence-nav/list-icon-visited.png
deleted file mode 100644
index 2df2935dbe..0000000000
Binary files a/lms/static/images_old/sequence-nav/list-icon-visited.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/next-icon.png b/lms/static/images_old/sequence-nav/next-icon.png
deleted file mode 100644
index 413ce38d1b..0000000000
Binary files a/lms/static/images_old/sequence-nav/next-icon.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/other-icon.png b/lms/static/images_old/sequence-nav/other-icon.png
deleted file mode 100644
index 822a415972..0000000000
Binary files a/lms/static/images_old/sequence-nav/other-icon.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/previous-icon.png b/lms/static/images_old/sequence-nav/previous-icon.png
deleted file mode 100644
index 0b3f369176..0000000000
Binary files a/lms/static/images_old/sequence-nav/previous-icon.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/problem-icon-alt.png b/lms/static/images_old/sequence-nav/problem-icon-alt.png
deleted file mode 100644
index c276ba223f..0000000000
Binary files a/lms/static/images_old/sequence-nav/problem-icon-alt.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/vertical-icon.png b/lms/static/images_old/sequence-nav/vertical-icon.png
deleted file mode 100644
index 6100b678c4..0000000000
Binary files a/lms/static/images_old/sequence-nav/vertical-icon.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/video-icon-current.png b/lms/static/images_old/sequence-nav/video-icon-current.png
deleted file mode 100644
index 8bbc910f61..0000000000
Binary files a/lms/static/images_old/sequence-nav/video-icon-current.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/video-icon-normal.png b/lms/static/images_old/sequence-nav/video-icon-normal.png
deleted file mode 100644
index 52321ebf7b..0000000000
Binary files a/lms/static/images_old/sequence-nav/video-icon-normal.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/video-icon-visited.png b/lms/static/images_old/sequence-nav/video-icon-visited.png
deleted file mode 100644
index e09aad8d20..0000000000
Binary files a/lms/static/images_old/sequence-nav/video-icon-visited.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/video-icon.png b/lms/static/images_old/sequence-nav/video-icon.png
deleted file mode 100644
index e68ec0f8ae..0000000000
Binary files a/lms/static/images_old/sequence-nav/video-icon.png and /dev/null differ
diff --git a/lms/static/images_old/sequence-nav/view.png b/lms/static/images_old/sequence-nav/view.png
deleted file mode 100644
index 62f04aa993..0000000000
Binary files a/lms/static/images_old/sequence-nav/view.png and /dev/null differ
diff --git a/lms/static/js/form.ext.js b/lms/static/js/form.ext.js
new file mode 100644
index 0000000000..db98f42ddb
--- /dev/null
+++ b/lms/static/js/form.ext.js
@@ -0,0 +1,44 @@
+(function($, undefined) {
+ var form_ext;
+ $.form_ext = form_ext = {
+ ajax: function(options) {
+ return $.ajax(options);
+ },
+ handleRemote: function(element) {
+ var method = element.attr('method');
+ var url = element.attr('action');
+ var data = element.serializeArray();
+ var options = {
+ type: method || 'GET',
+ data: data,
+ dataType: 'text json',
+ success: function(data, status, xhr) {
+ element.trigger("ajax:success", [data, status, xhr]);
+ },
+ complete: function(xhr, status) {
+ element.trigger("ajax:complete", [xhr, status]);
+ },
+ error: function(xhr, status, error) {
+ element.trigger("ajax:error", [xhr, status, error]);
+ }
+ }
+ if(url) { options.url = url; }
+ return form_ext.ajax(options)
+ },
+ CSRFProtection: function(xhr) {
+ var token = $.cookie('csrftoken');
+ if (token) xhr.setRequestHeader('X-CSRFToken', token);
+ },
+ }
+ $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { form_ext.CSRFProtection(xhr); }});
+ $(document).delegate('form', 'submit', function(e) {
+ var form = $(this),
+ remote = form.data("remote") !== undefined;
+
+ if(remote) {
+ form_ext.handleRemote(form);
+ return false;
+ }
+
+ });
+})(jQuery);
diff --git a/lms/static/js/toggle_login_modal.js b/lms/static/js/toggle_login_modal.js
index 59d83a0dad..df62a01505 100644
--- a/lms/static/js/toggle_login_modal.js
+++ b/lms/static/js/toggle_login_modal.js
@@ -7,9 +7,11 @@
closeButton: null,
position: 'fixed'
}
-
- var overlay = $("
");
- $("body").append(overlay);
+
+ if ($("#lean_overlay").length == 0) {
+ var overlay = $("
");
+ $("body").append(overlay);
+ }
options = $.extend(defaults, options);
@@ -21,6 +23,17 @@
$(".modal").hide();
var modal_id = $(this).attr("href");
+
+ if ($(modal_id).hasClass("video-modal")) {
+ //Video modals need to be cloned before being presented as a modal
+ //This is because actions on the video get recorded in the history.
+ //Deleting the video (clone) prevents the odd back button behavior.
+ var modal_clone = $(modal_id).clone(true, true);
+ modal_clone.attr('id', 'modal_clone');
+ $(modal_id).after(modal_clone);
+ modal_id = '#modal_clone';
+ }
+
$("#lean_overlay").click(function() {
close_modal(modal_id);
@@ -48,6 +61,16 @@
})
$(modal_id).fadeTo(200,1);
+ $(modal_id).find(".notice").hide().html("");
+ var notice = $(this).data('notice')
+ if(notice !== undefined) {
+ $notice = $(modal_id).find(".notice");
+ $notice.show().html(notice);
+ // This is for activating leanModal links that were in the notice. We should have a cleaner way of
+ // allowing all dynamically added leanmodal links to work.
+ $notice.find("a[rel*=leanModal]").leanModal({ top : 120, overlay: 1, closeButton: ".close-modal", position: 'absolute' });
+ }
+ window.scrollTo(0, 0);
e.preventDefault();
});
@@ -57,6 +80,9 @@
$("#lean_overlay").fadeOut(200);
$('iframe', modal_id).attr('src', '');
$(modal_id).css({ 'display' : 'none' });
+ if (modal_id == '#modal_clone') {
+ $(modal_id).remove();
+ }
}
}
});
@@ -65,8 +91,13 @@
$(this).leanModal({ top : 120, overlay: 1, closeButton: ".close-modal", position: 'absolute' });
embed = $($(this).attr('href')).find('iframe')
if(embed.length > 0) {
- embed.data('src', embed.attr('src') + '?autoplay=1');
- embed.attr('src', '');
+ if(embed.attr('src').indexOf("?") > 0) {
+ embed.data('src', embed.attr('src') + '&autoplay=1&rel=0');
+ embed.attr('src', '');
+ } else {
+ embed.data('src', embed.attr('src') + '?autoplay=1&rel=0');
+ embed.attr('src', '');
+ }
}
});
})(jQuery);
diff --git a/lms/static/js/vendor/CodeMirror/codemirror.css b/lms/static/js/vendor/CodeMirror/codemirror.css
deleted file mode 100644
index 2d79f4aa79..0000000000
--- a/lms/static/js/vendor/CodeMirror/codemirror.css
+++ /dev/null
@@ -1,112 +0,0 @@
-.CodeMirror {
- line-height: 1em;
- font-family: monospace;
-}
-
-.CodeMirror-scroll {
- overflow: auto;
- height: 300px;
- /* This is needed to prevent an IE[67] bug where the scrolled content
- is visible outside of the scrolling box. */
- position: relative;
- outline: none;
-}
-
-.CodeMirror-gutter {
- position: absolute; left: 0; top: 0;
- z-index: 10;
- background-color: #f7f7f7;
- border-right: 1px solid #eee;
- min-width: 2em;
- height: 100%;
-}
-.CodeMirror-gutter-text {
- color: #aaa;
- text-align: right;
- padding: .4em .2em .4em .4em;
- white-space: pre !important;
-}
-.CodeMirror-lines {
- padding: .4em;
- white-space: pre;
-}
-
-.CodeMirror pre {
- -moz-border-radius: 0;
- -webkit-border-radius: 0;
- -o-border-radius: 0;
- border-radius: 0;
- border-width: 0; margin: 0; padding: 0; background: transparent;
- font-family: inherit;
- font-size: inherit;
- padding: 0; margin: 0;
- white-space: pre;
- word-wrap: normal;
-}
-
-.CodeMirror-wrap pre {
- word-wrap: break-word;
- white-space: pre-wrap;
-}
-.CodeMirror-wrap .CodeMirror-scroll {
- overflow-x: hidden;
-}
-
-.CodeMirror textarea {
- outline: none !important;
-}
-
-.CodeMirror pre.CodeMirror-cursor {
- z-index: 10;
- position: absolute;
- visibility: hidden;
- border-left: 1px solid black;
- border-right:none;
- width:0;
-}
-.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
-.CodeMirror-focused pre.CodeMirror-cursor {
- visibility: visible;
-}
-
-div.CodeMirror-selected { background: #d9d9d9; }
-.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
-
-.CodeMirror-searching {
- background: #ffa;
- background: rgba(255, 255, 0, .4);
-}
-
-/* Default theme */
-
-.cm-s-default span.cm-keyword {color: #708;}
-.cm-s-default span.cm-atom {color: #219;}
-.cm-s-default span.cm-number {color: #164;}
-.cm-s-default span.cm-def {color: #00f;}
-.cm-s-default span.cm-variable {color: black;}
-.cm-s-default span.cm-variable-2 {color: #05a;}
-.cm-s-default span.cm-variable-3 {color: #085;}
-.cm-s-default span.cm-property {color: black;}
-.cm-s-default span.cm-operator {color: black;}
-.cm-s-default span.cm-comment {color: #a50;}
-.cm-s-default span.cm-string {color: #a11;}
-.cm-s-default span.cm-string-2 {color: #f50;}
-.cm-s-default span.cm-meta {color: #555;}
-.cm-s-default span.cm-error {color: #f00;}
-.cm-s-default span.cm-qualifier {color: #555;}
-.cm-s-default span.cm-builtin {color: #30a;}
-.cm-s-default span.cm-bracket {color: #cc7;}
-.cm-s-default span.cm-tag {color: #170;}
-.cm-s-default span.cm-attribute {color: #00c;}
-.cm-s-default span.cm-header {color: #a0a;}
-.cm-s-default span.cm-quote {color: #090;}
-.cm-s-default span.cm-hr {color: #999;}
-.cm-s-default span.cm-link {color: #00c;}
-
-span.cm-header, span.cm-strong {font-weight: bold;}
-span.cm-em {font-style: italic;}
-span.cm-emstrong {font-style: italic; font-weight: bold;}
-span.cm-link {text-decoration: underline;}
-
-div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
-div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
diff --git a/lms/static/js/vendor/jquery.cookie.js b/lms/static/js/vendor/jquery.cookie.js
deleted file mode 100644
index 6d5974a2c5..0000000000
--- a/lms/static/js/vendor/jquery.cookie.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*!
- * jQuery Cookie Plugin
- * https://github.com/carhartl/jquery-cookie
- *
- * Copyright 2011, Klaus Hartl
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.opensource.org/licenses/GPL-2.0
- */
-(function($) {
- $.cookie = function(key, value, options) {
-
- // key and at least value given, set cookie...
- if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {
- options = $.extend({}, options);
-
- if (value === null || value === undefined) {
- options.expires = -1;
- }
-
- if (typeof options.expires === 'number') {
- var days = options.expires, t = options.expires = new Date();
- t.setDate(t.getDate() + days);
- }
-
- value = String(value);
-
- return (document.cookie = [
- encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),
- options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
- options.path ? '; path=' + options.path : '',
- options.domain ? '; domain=' + options.domain : '',
- options.secure ? '; secure' : ''
- ].join(''));
- }
-
- // key and possibly options given, get cookie...
- options = value || {};
- var decode = options.raw ? function(s) { return s; } : decodeURIComponent;
-
- var pairs = document.cookie.split('; ');
- for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {
- if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined
- }
- return null;
- };
-})(jQuery);
diff --git a/lms/static/js/vendor/jquery.min.js b/lms/static/js/vendor/jquery.min.js
deleted file mode 100644
index 16ad06c5ac..0000000000
--- a/lms/static/js/vendor/jquery.min.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/*! jQuery v1.7.2 jquery.com | jquery.org/license */
-(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;ca ",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q=""+"",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(
-a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c ",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML=" ",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="
";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/ ]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,""," "],thead:[1,""],tr:[2,""],td:[3,""],col:[2,""],area:[1,""," "],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div","
"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f
-.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>$2>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/
+ <%static:css group='course'/>
%block>
<%block name="js_extra">
-
-
+
+
+ ## codemirror
+
+
+ ## alternate codemirror
+ ##
+ ##
+ ##
+
+ ## image input: for clicking on images (see imageinput.html)
+
+
+ ## TODO (cpennington): Remove this when we have a good way for modules to specify js to load on the page
+ ## and in the wiki
+
+
+
+ <%static:js group='courseware'/>
+
+ <%include file="mathjax_include.html" />
+
+
+
%block>
<%include file="course_navigation.html" args="active_page='courseware'" />
diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html
index 48579d7117..ca3b273fc3 100644
--- a/lms/templates/dashboard.html
+++ b/lms/templates/dashboard.html
@@ -1,27 +1,58 @@
-<%! from django.core.urlresolvers import reverse %>
+<%!
+ from django.core.urlresolvers import reverse
+ from courseware.courses import course_image_url, get_course_about_section
+%>
<%inherit file="main.html" />
<%namespace name='static' file='static_content.html'/>
+<%block name="title">Dashboard %block>
+
+<%block name="js_extra">
+
+%block>
+
+
+
+
+
+
+
+ Are you sure you want to unregister from ?
+
+
+
+
+
+
+
+
+
+
diff --git a/lms/templates/emails/activation_email.txt b/lms/templates/emails/activation_email.txt
index 5d5f980156..209ff98335 100644
--- a/lms/templates/emails/activation_email.txt
+++ b/lms/templates/emails/activation_email.txt
@@ -1,7 +1,6 @@
-Someone, hopefully you, signed up for an account for edX's on-line
-offering of "${ course_title}" using this email address. If it was
-you, and you'd like to activate and use your account, copy and paste
-this address into your web browser's address bar:
+Thank you for signing up for edX! To activate your account,
+please copy and paste this address into your web browser's
+address bar:
% if is_secure:
https://${ site }/activate/${ key }
diff --git a/lms/templates/emails/confirm_email_change.txt b/lms/templates/emails/confirm_email_change.txt
index e625878f4f..e1b5d63376 100644
--- a/lms/templates/emails/confirm_email_change.txt
+++ b/lms/templates/emails/confirm_email_change.txt
@@ -1,5 +1,5 @@
<%! from django.core.urlresolvers import reverse %>
-This is to confirm that you changed the e-mail associated with MITx
+This is to confirm that you changed the e-mail associated with edX
from ${old_email} to ${new_email}. If you did not make this request,
please contact the course staff immediately. Contact information is
listed at:
diff --git a/lms/templates/emails/email_change.txt b/lms/templates/emails/email_change.txt
index 51be82f564..8f8f711083 100644
--- a/lms/templates/emails/email_change.txt
+++ b/lms/templates/emails/email_change.txt
@@ -1,4 +1,4 @@
-We received a request to change the e-mail associated with your MITx
+We received a request to change the e-mail associated with your edX
account from ${old_email} to ${new_email}. If this is correct, please
confirm your new e-mail address by visiting:
@@ -10,4 +10,4 @@ confirm your new e-mail address by visiting:
If you didn't request this, you don't need to do anything; you won't
receive any more email from us. Please do not reply to this e-mail; if
-you require assistance, check the help section of the MITx web site.
+you require assistance, check the help section of the edX web site.
diff --git a/lms/templates/emails/email_change_subject.txt b/lms/templates/emails/email_change_subject.txt
index 6ab38757f6..599d49ee65 100644
--- a/lms/templates/emails/email_change_subject.txt
+++ b/lms/templates/emails/email_change_subject.txt
@@ -1 +1 @@
-Request to change MITx account e-mail
+Request to change edX account e-mail
diff --git a/lms/templates/emails/welcome_body.txt b/lms/templates/emails/welcome_body.txt
index 3944f6c325..8a891e0b80 100644
--- a/lms/templates/emails/welcome_body.txt
+++ b/lms/templates/emails/welcome_body.txt
@@ -1,28 +1,20 @@
-MITx's prototype offering, 6.002x, is open. To log in, visit
+edX has launched! To log in, visit:
% if is_secure:
- https://6002x.mitx.mit.edu
+ https://edx.org
% else:
- http://6002x.mitx.mit.edu
+ http://edx.org
% endif
-where you will find a login button at the top right-hand corner of the
-window.
+A login button will be at the top right-hand corner of the window.
Please make sure you're using the latest version of Google Chrome or
Firefox. If you've forgotten your password, the log-in form has a
place to reset it.
-Once you log in, we recommend that you start the course by reviewing
-the "System Usage Sequence" in the Overview section, and the "6.002x
-At-a-Glance (Calendar)" handout under the Course Info tab. After you
-familiarize yourself with the features of the MITx platform,
-you can jump right into the coursework by working on "Administrivia
-and Circuit Elements", the first Lecture Sequence in Week 1.
-
Thanks for joining us for the ride!
-The 6.002x team
+The edX team
(Please note that this e-mail address does not receive e-mails --
if you need assistance, please use the help section of the web
diff --git a/lms/templates/emails/welcome_subject.txt b/lms/templates/emails/welcome_subject.txt
index a905585689..748ccfe113 100644
--- a/lms/templates/emails/welcome_subject.txt
+++ b/lms/templates/emails/welcome_subject.txt
@@ -1 +1 @@
-Welcome to 6.002x!
+Welcome to edX!
diff --git a/lms/templates/feed.rss b/lms/templates/feed.rss
new file mode 100644
index 0000000000..c73eb8814a
--- /dev/null
+++ b/lms/templates/feed.rss
@@ -0,0 +1,35 @@
+<%! from django.core.urlresolvers import reverse %>\
+
+<%namespace name='static' file='static_content.html'/>
+
+ tag:www.edx.org,2012:/blog
+
+ ##
+ EdX Blog
+ 2012-07-16T14:08:12-07:00
+
+ tag:www.edx.org,2012:Post/3
+ 2012-07-16T14:08:12-07:00
+ 2012-07-16T14:08:12-07:00
+
+ UC Berkeley joins edX
+ <img src="${static.url('images/edx.png')}" />
+ <p>edX broadens course offerings</p>
+
+
+ tag:www.edx.org,2012:Post/2
+ 2012-07-16T14:08:12-07:00
+ 2012-07-16T14:08:12-07:00
+
+ Opening Doors For Exceptional Students: 6.002x in Mongolia
+ <img src="${static.url('images/press/tumblr_tumbnail_opening_doors_mongolia.jpg')}" />
+
+
+ tag:www.edx.org,2012:Post/1
+ 2012-07-16T14:08:12-07:00
+ 2012-07-16T14:08:12-07:00
+
+ Brazilian teen blogs about his 6.002x experience
+ <img src="${static.url('images/press/tumblr_tumbnail_brazilian_teen.jpg')}" />
+
+
diff --git a/lms/templates/footer.html b/lms/templates/footer.html
index 69355fa56e..c1fc3c3f36 100644
--- a/lms/templates/footer.html
+++ b/lms/templates/footer.html
@@ -8,21 +8,23 @@
Find Courses
About
- Blog
+ Blog
Jobs
Contact
diff --git a/lms/templates/forgot_password_modal.html b/lms/templates/forgot_password_modal.html
new file mode 100644
index 0000000000..97b41d4be9
--- /dev/null
+++ b/lms/templates/forgot_password_modal.html
@@ -0,0 +1,44 @@
+<%! from django.core.urlresolvers import reverse %>
+
+
+
+
+
+
+
Enter your e-mail address below, and we will e-mail instructions for setting a new password.
+
+
+
+
+
+
+
+
+
+
diff --git a/lms/templates/gradebook.html b/lms/templates/gradebook.html
index 12f820c8c9..7c2908dc77 100644
--- a/lms/templates/gradebook.html
+++ b/lms/templates/gradebook.html
@@ -1,10 +1,13 @@
<%inherit file="main.html" />
<%namespace name='static' file='static_content.html'/>
-<%block name="headextra">
+<%block name="js_extra">
+%block>
+
+<%block name="headextra">