get relationship mapper to work even without morphology extension
[scpubgit/stemmaweb.git] / root / js / relationship-full.js
1 var MARGIN=30;
2 var svg_root = null;
3 var svg_root_element = null;
4 var start_element_height = 0;
5 var reltypes = {};
6 var readingdata = {};
7
8 function getTextURL( which ) {
9         return basepath + textid + '/' + which;
10 }
11
12 function getReadingURL( reading_id ) {
13         return basepath + textid + '/reading/' + reading_id;
14 }
15
16 // Make an XML ID into a valid selector
17 function jq(myid) { 
18         return '#' + myid.replace(/(:|\.)/g,'\\$1');
19 }
20
21 // Actions for opening the reading panel
22 function node_dblclick_listener( evt ) {
23         // Open the reading dialogue for the given node.
24         // First get the reading info
25         var reading_id = $(this).attr('id');
26         var reading_info = readingdata[reading_id];
27         // and then populate the dialog box with it.
28         // Set the easy properties first
29         $('#reading-form').dialog( 'option', 'title', 'Reading information for "' + reading_info['text'] + '"' );
30         $('#reading_id').val( reading_id );
31         toggle_checkbox( $('#reading_is_nonsense'), reading_info['is_nonsense'] );
32         toggle_checkbox( $('#reading_grammar_invalid'), reading_info['grammar_invalid'] );
33         // Use .text as a backup for .normal_form
34         var normal_form = reading_info['normal_form'];
35         if( !normal_form ) {
36                 normal_form = reading_info['text'];
37         }
38         var nfboxsize = 10;
39         if( normal_form.length > 9 ) {
40                 nfboxsize = normal_form.length + 1;
41         }
42         $('#reading_normal_form').attr( 'size', nfboxsize )
43         $('#reading_normal_form').val( normal_form );
44         // Now do the morphological properties.
45         morphology_form( reading_info['lexemes'] );
46         // and then open the dialog.
47         $('#reading-form').dialog("open");
48 }
49
50 function toggle_checkbox( box, value ) {
51         if( value == null ) {
52                 value = false;
53         }
54         box.attr('checked', value );
55 }
56
57 function morphology_form ( lexlist ) {
58         $('#morphology').empty();
59         $.each( lexlist, function( idx, lex ) {
60                 var morphoptions = [];
61                 if( 'wordform_matchlist' in lex ) {
62                         $.each( lex['wordform_matchlist'], function( tdx, tag ) {
63                                 var tagstr = stringify_wordform( tag );
64                                 morphoptions.push( tagstr );
65                         });
66                 }
67                 var formtag = 'morphology_' + idx;
68                 var formstr = '';
69                 if( 'form' in lex ) {
70                         formstr = stringify_wordform( lex['form'] );
71                 } 
72                 var form_morph_elements = morph_elements( 
73                         formtag, lex['string'], formstr, morphoptions );
74                 $.each( form_morph_elements, function( idx, el ) {
75                         $('#morphology').append( el );
76                 });
77         });
78 }
79
80 function stringify_wordform ( tag ) {
81         if( tag ) {
82                 var elements = tag.split(' // ');
83                 return elements[1] + ' // ' + elements[2];
84         }
85         return ''
86 }
87
88 function morph_elements ( formtag, formtxt, currform, morphoptions ) {
89         var clicktag = '(Click to select)';
90         if ( !currform ) {
91                 currform = clicktag;
92         }
93         var formlabel = $('<label/>').attr( 'id', 'label_' + formtag ).attr( 
94                 'for', 'reading_' + formtag ).text( formtxt + ': ' );
95         var forminput = $('<input/>').attr( 'id', 'reading_' + formtag ).attr( 
96                 'name', 'reading_' + formtag ).attr( 'size', '50' ).attr(
97                 'class', 'reading_morphology' ).val( currform );
98         forminput.autocomplete({ source: morphoptions, minLength: 0     });
99         forminput.focus( function() { 
100                 if( $(this).val() == clicktag ) {
101                         $(this).val('');
102                 }
103                 $(this).autocomplete('search', '') 
104         });
105         var morphel = [ formlabel, forminput, $('<br/>') ];
106         return morphel;
107 }
108
109 function color_inactive ( el ) {
110         var reading_id = $(el).parent().attr('id');
111         var reading_info = readingdata[reading_id];
112         // If the reading info has any non-disambiguated lexemes, color it yellow;
113         // otherwise color it green.
114         $(el).attr( {stroke:'green', fill:'#b3f36d'} );
115         if( reading_info ) {
116                 $.each( reading_info['lexemes'], function ( idx, lex ) {
117                         if( !lex['is_disambiguated'] || lex['is_disambiguated'] == 0 ) {
118                                 $(el).attr( {stroke:'orange', fill:'#fee233'} );
119                         }
120                 });
121         }
122 }
123
124 function relemmatize () {
125         // Send the reading for a new lemmatization and reopen the form.
126         $('#relemmatize_pending').show();
127         var reading_id = $('#reading_id').val()
128         ncpath = getReadingURL( reading_id );
129         form_values = { 
130                 'normal_form': $('#reading_normal_form').val(), 
131                 'relemmatize': 1 };
132         var jqjson = $.post( ncpath, form_values, function( data ) {
133                 // Update the form with the return
134                 if( 'id' in data ) {
135                         // We got back a good answer. Stash it
136                         readingdata[reading_id] = data;
137                         // and regenerate the morphology form.
138                         morphology_form( data['lexemes'] );
139                 } else {
140                         alert("Could not relemmatize as requested: " + data['error']);
141                 }
142                 $('#relemmatize_pending').hide();
143         });
144 }
145
146 // Initialize the SVG once it exists
147 function svgEnlargementLoaded() {
148         //Give some visual evidence that we are working
149         $('#loading_overlay').show();
150         lo_height = $("#enlargement_container").outerHeight();
151         lo_width = $("#enlargement_container").outerWidth();
152         $("#loading_overlay").height( lo_height );
153         $("#loading_overlay").width( lo_width );
154         $("#loading_overlay").offset( $("#enlargement_container").offset() );
155         $("#loading_message").offset(
156                 { 'top': lo_height / 2 - $("#loading_message").height() / 2,
157                   'left': lo_width / 2 - $("#loading_message").width() / 2 });
158     //Set viewbox widht and height to widht and height of $('#svgenlargement svg').
159     $('#update_workspace_button').data('locked', false);
160     $('#update_workspace_button').css('background-position', '0px 44px');
161     //This is essential to make sure zooming and panning works properly.
162         var rdgpath = getTextURL( 'readings' );
163                 $.getJSON( rdgpath, function( data ) {
164                 readingdata = data;
165             $('#svgenlargement ellipse').each( function( i, el ) { color_inactive( el ) });
166         });
167     $('#svgenlargement ellipse').parent().dblclick( node_dblclick_listener );
168     var graph_svg = $('#svgenlargement svg');
169     var svg_g = $('#svgenlargement svg g')[0];
170     if (!svg_g) return;
171     svg_root = graph_svg.svg().svg('get').root();
172
173     // Find the real root and ignore any text nodes
174     for (i = 0; i < svg_root.childNodes.length; ++i) {
175         if (svg_root.childNodes[i].nodeName != '#text') {
176                 svg_root_element = svg_root.childNodes[i];
177                 break;
178            }
179     }
180
181     svg_root.viewBox.baseVal.width = graph_svg.attr( 'width' );
182     svg_root.viewBox.baseVal.height = graph_svg.attr( 'height' );
183     //Now set scale and translate so svg height is about 150px and vertically centered in viewbox.
184     //This is just to create a nice starting enlargement.
185     var initial_svg_height = 250;
186     var scale = initial_svg_height/graph_svg.attr( 'height' );
187     var additional_translate = (graph_svg.attr( 'height' ) - initial_svg_height)/(2*scale);
188     var transform = svg_g.getAttribute('transform');
189     var translate = parseFloat( transform.match( /translate\([^\)]*\)/ )[0].split('(')[1].split(' ')[1].split(')')[0] );
190     translate += additional_translate;
191     var transform = 'rotate(0) scale(' + scale + ') translate(4 ' + translate + ')';
192     svg_g.setAttribute('transform', transform);
193     //used to calculate min and max zoom level:
194     start_element_height = $('#__START__').children('ellipse')[0].getBBox().height;
195     add_relations( function() { $('#loading_overlay').hide(); });
196 }
197
198 function add_relations( callback_fn ) {
199         var textrelpath = getTextURL( 'relationships' );
200     $.getJSON( basepath + 'definitions', function(data) {
201         var rel_types = data.types.sort();
202         $.getJSON( textrelpath,
203         function(data) {
204             $.each(data, function( index, rel_info ) {
205                 var type_index = $.inArray(rel_info.type, rel_types);
206                 var source_found = get_ellipse( rel_info.source );
207                 var target_found = get_ellipse( rel_info.target );
208                 if( type_index != -1 && source_found.size() && target_found.size() ) {
209                     var relation = relation_manager.create( rel_info.source, rel_info.target, type_index );
210                     relation.data( 'type', rel_info.type );
211                     relation.data( 'scope', rel_info.scope );
212                     relation.data( 'note', rel_info.note );
213                     var node_obj = get_node_obj(rel_info.source);
214                     node_obj.set_draggable( false );
215                     node_obj.ellipse.data( 'node_obj', null );
216                     node_obj = get_node_obj(rel_info.target);
217                     node_obj.set_draggable( false );
218                     node_obj.ellipse.data( 'node_obj', null );
219                 }
220             });
221             callback_fn.call();
222         });
223     });
224 }
225
226 function get_ellipse( node_id ) {
227         return $( jq( node_id ) + ' ellipse');
228 }
229
230 function get_node_obj( node_id ) {
231     var node_ellipse = get_ellipse( node_id );
232     if( node_ellipse.data( 'node_obj' ) == null ) {
233         node_ellipse.data( 'node_obj', new node_obj(node_ellipse) );
234     };
235     return node_ellipse.data( 'node_obj' );
236 }
237
238 function node_obj(ellipse) {
239   this.ellipse = ellipse;
240   var self = this;
241   
242   this.x = 0;
243   this.y = 0;
244   this.dx = 0;
245   this.dy = 0;
246   this.node_elements = node_elements_for(self.ellipse);
247
248   this.get_id = function() {
249     return $(self.ellipse).parent().attr('id')
250   }
251   
252   this.set_draggable = function( draggable ) {
253     if( draggable ) {
254       $(self.ellipse).attr( {stroke:'black', fill:'#fff'} );
255       $(self.ellipse).parent().mousedown( this.mousedown_listener );
256       $(self.ellipse).parent().hover( this.enter_node, this.leave_node ); 
257       self.ellipse.siblings('text').attr('class', 'noselect draggable');
258     } else {
259       self.ellipse.siblings('text').attr('class', '');
260           $(self.ellipse).parent().unbind( 'mouseenter' ).unbind( 'mouseleave' ).unbind( 'mousedown' );     
261       color_inactive( self.ellipse );
262     }
263   }
264
265   this.mousedown_listener = function(evt) {
266     evt.stopPropagation();
267     self.x = evt.clientX;
268     self.y = evt.clientY;
269     $('body').mousemove( self.mousemove_listener );
270     $('body').mouseup( self.mouseup_listener );
271     $(self.ellipse).parent().unbind('mouseenter').unbind('mouseleave')
272     self.ellipse.attr( 'fill', '#ff66ff' );
273     first_node_g_element = $("#svgenlargement g .node" ).filter( ":first" );
274     if( first_node_g_element.attr('id') !== self.get_g().attr('id') ) { self.get_g().insertBefore( first_node_g_element ) };
275   }
276
277   this.mousemove_listener = function(evt) {
278     self.dx = (evt.clientX - self.x) / mouse_scale;
279     self.dy = (evt.clientY - self.y) / mouse_scale;
280     self.move_elements();
281     evt.returnValue = false;
282     evt.preventDefault();
283     return false;
284   }
285
286   this.mouseup_listener = function(evt) {    
287     if( $('ellipse[fill="#ffccff"]').size() > 0 ) {
288         var source_node_id = $(self.ellipse).parent().attr('id');
289         var source_node_text = self.ellipse.siblings('text').text();
290         var target_node_id = $('ellipse[fill="#ffccff"]').parent().attr('id');
291         var target_node_text = $('ellipse[fill="#ffccff"]').siblings("text").text();
292         $('#source_node_id').val( source_node_id );
293         $('#source_node_text').val( source_node_text );
294         $('#target_node_id').val( target_node_id );
295         $('#target_node_text').val( target_node_text );
296         $('#dialog-form').dialog( 'open' );
297     };
298     $('body').unbind('mousemove');
299     $('body').unbind('mouseup');
300     self.ellipse.attr( 'fill', '#fff' );
301     $(self.ellipse).parent().hover( self.enter_node, self.leave_node );
302     self.reset_elements();
303   }
304   
305   this.cpos = function() {
306     return { x: self.ellipse.attr('cx'), y: self.ellipse.attr('cy') };
307   }
308
309   this.get_g = function() {
310     return self.ellipse.parent('g');
311   }
312
313   this.enter_node = function(evt) {
314     self.ellipse.attr( 'fill', '#ffccff' );
315   }
316
317   this.leave_node = function(evt) {
318     self.ellipse.attr( 'fill', '#fff' );
319   }
320
321   this.greyout_edges = function() {
322       $.each( self.node_elements, function(index, value) {
323         value.grey_out('.edge');
324       });
325   }
326
327   this.ungreyout_edges = function() {
328       $.each( self.node_elements, function(index, value) {
329         value.un_grey_out('.edge');
330       });
331   }
332
333   this.move_elements = function() {
334     $.each( self.node_elements, function(index, value) {
335       value.move(self.dx,self.dy);
336     });
337   }
338
339   this.reset_elements = function() {
340     $.each( self.node_elements, function(index, value) {
341       value.reset();
342     });
343   }
344
345   this.update_elements = function() {
346       self.node_elements = node_elements_for(self.ellipse);
347   }
348
349   self.set_draggable( true );
350 }
351
352 function svgshape( shape_element ) {
353   this.shape = shape_element;
354   this.move = function(dx,dy) {
355     this.shape.attr( "transform", "translate(" + dx + " " + dy + ")" );
356   }
357   this.reset = function() {
358     this.shape.attr( "transform", "translate( 0, 0 )" );
359   }
360   this.grey_out = function(filter) {
361       if( this.shape.parent(filter).size() != 0 ) {
362           this.shape.attr({'stroke':'#e5e5e5', 'fill':'#e5e5e5'});
363       }
364   }
365   this.un_grey_out = function(filter) {
366       if( this.shape.parent(filter).size() != 0 ) {
367         this.shape.attr({'stroke':'#000000', 'fill':'#000000'});
368       }
369   }
370 }
371
372 function svgpath( path_element, svg_element ) {
373   this.svg_element = svg_element;
374   this.path = path_element;
375   this.x = this.path.x;
376   this.y = this.path.y;
377   this.move = function(dx,dy) {
378     this.path.x = this.x + dx;
379     this.path.y = this.y + dy;
380   }
381   this.reset = function() {
382     this.path.x = this.x;
383     this.path.y = this.y;
384   }
385   this.grey_out = function(filter) {
386       if( this.svg_element.parent(filter).size() != 0 ) {
387           this.svg_element.attr('stroke', '#e5e5e5');
388           this.svg_element.siblings('text').attr('fill', '#e5e5e5');
389           this.svg_element.siblings('text').attr('class', 'noselect');
390       }
391   }
392   this.un_grey_out = function(filter) {
393       if( this.svg_element.parent(filter).size() != 0 ) {
394           this.svg_element.attr('stroke', '#000000');
395           this.svg_element.siblings('text').attr('fill', '#000000');
396           this.svg_element.siblings('text').attr('class', '');
397       }
398   }
399 }
400
401 function node_elements_for( ellipse ) {
402   node_elements = get_edge_elements_for( ellipse );
403   node_elements.push( new svgshape( ellipse.siblings('text') ) );
404   node_elements.push( new svgshape( ellipse ) );
405   return node_elements;
406 }
407
408 function get_edge_elements_for( ellipse ) {
409   edge_elements = new Array();
410   node_id = ellipse.parent().attr('id');
411   edge_in_pattern = new RegExp( node_id + '$' );
412   edge_out_pattern = new RegExp( '^' + node_id );
413   $.each( $('#svgenlargement .edge,#svgenlargement .relation').children('title'), function(index) {
414     title = $(this).text();
415     if( edge_in_pattern.test(title) ) {
416         polygon = $(this).siblings('polygon');
417         if( polygon.size() > 0 ) {
418             edge_elements.push( new svgshape( polygon ) );
419         }
420         path_segments = $(this).siblings('path')[0].pathSegList;
421         edge_elements.push( new svgpath( path_segments.getItem(path_segments.numberOfItems - 1), $(this).siblings('path') ) );
422     }
423     if( edge_out_pattern.test(title) ) {
424       path_segments = $(this).siblings('path')[0].pathSegList;
425       edge_elements.push( new svgpath( path_segments.getItem(0), $(this).siblings('path') ) );
426     }
427   });
428   return edge_elements;
429
430
431 function relation_factory() {
432     var self = this;
433     this.color_memo = null;
434     //TODO: colors hard coded for now
435     this.temp_color = '#FFA14F';
436     this.relation_colors = [ "#5CCCCC", "#67E667", "#F9FE72", "#6B90D4", "#FF7673", "#E467B3", "#AA67D5", "#8370D8", "#FFC173" ];
437
438     this.create_temporary = function( source_node_id, target_node_id ) {
439         var relation_id = get_relation_id( source_node_id, target_node_id );
440         var relation = $( jq( relation_id ) );
441         if( relation.size() == 0 ) { 
442             draw_relation( source_node_id, target_node_id, self.temp_color );
443         } else {
444             self.color_memo = relation.children('path').attr( 'stroke' );
445             relation.children('path').attr( 'stroke', self.temp_color );
446         }
447     }
448     this.remove_temporary = function() {
449         var path_element = $('#svgenlargement .relation').children('path[stroke="' + self.temp_color + '"]');
450         if( self.color_memo != null ) {
451             path_element.attr( 'stroke', self.color_memo );
452             self.color_memo = null;
453         } else {
454             var temporary = path_element.parent('g').remove();
455             temporary.empty();
456             temporary = null; 
457         }
458     }
459     this.create = function( source_node_id, target_node_id, color_index ) {
460         //TODO: Protect from (color_)index out of bound..
461         var relation_color = self.relation_colors[ color_index ];
462         var relation = draw_relation( source_node_id, target_node_id, relation_color );
463         get_node_obj( source_node_id ).update_elements();
464         get_node_obj( target_node_id ).update_elements();
465         return relation;
466     }
467     this.toggle_active = function( relation_id ) {
468         var relation = $( jq( relation_id ) );
469         var relation_path = relation.children('path');
470         if( !relation.data( 'active' ) ) {
471             relation_path.css( {'cursor':'pointer'} );
472             relation_path.mouseenter( function(event) { 
473                 outerTimer = setTimeout( function() { 
474                     timer = setTimeout( function() { 
475                         var related_nodes = get_related_nodes( relation_id );
476                         var source_node_id = related_nodes[0];
477                         var target_node_id = related_nodes[1];
478                         $('#delete_source_node_id').val( source_node_id );
479                         $('#delete_target_node_id').val( target_node_id );
480                         self.showinfo(relation); 
481                     }, 500 ) 
482                 }, 1000 );
483             });
484             relation_path.mouseleave( function(event) {
485                 clearTimeout(outerTimer); 
486                 if( timer != null ) { clearTimeout(timer); } 
487             });
488             relation.data( 'active', true );
489         } else {
490             relation_path.unbind( 'mouseenter' );
491             relation_path.unbind( 'mouseleave' );
492             relation_path.css( {'cursor':'inherit'} );
493             relation.data( 'active', false );
494         }
495     }
496     this.showinfo = function(relation) {
497         var htmlstr = 'type: ' + relation.data( 'type' ) + '<br/>scope: ' + relation.data( 'scope' );
498         if( relation.data( 'note' ) ) {
499                 htmlstr = htmlstr + '<br/>note: ' + relation.data( 'note' );
500         }
501         $('#delete-form-text').html( htmlstr );
502         var points = relation.children('path').attr('d').slice(1).replace('C',' ').split(' ');
503         var xs = parseFloat( points[0].split(',')[0] );
504         var xe = parseFloat( points[1].split(',')[0] );
505         var ys = parseFloat( points[0].split(',')[1] );
506         var ye = parseFloat( points[3].split(',')[1] );
507         var p = svg_root.createSVGPoint();
508         p.x = xs + ((xe-xs)*1.1);
509         p.y = ye - ((ye-ys)/2);
510         var ctm = svg_root_element.getScreenCTM();
511         var nx = p.matrixTransform(ctm).x;
512         var ny = p.matrixTransform(ctm).y;
513         var dialog_aria = $ ("div[aria-labelledby='ui-dialog-title-delete-form']");
514         $('#delete-form').dialog( 'open' );
515         dialog_aria.offset({ left: nx, top: ny });
516     }
517     this.remove = function( relation_id ) {
518         var relation = $( jq( relation_id ) );
519         relation.remove();
520     }
521 }
522
523 // Utility function to create/return the ID of a relation link between
524 // a source and target.
525 function get_relation_id( source_id, target_id ) {
526         var idlist = [ source_id, target_id ];
527         idlist.sort();
528         return 'relation-' + idlist[0] + '-...-' + idlist[1];
529 }
530
531 function get_related_nodes( relation_id ) {
532         var srctotarg = relation_id.substr( 9 );
533         return srctotarg.split('-...-');
534 }
535
536 function draw_relation( source_id, target_id, relation_color ) {
537     var source_ellipse = get_ellipse( source_id );
538     var target_ellipse = get_ellipse( target_id );
539     var relation_id = get_relation_id( source_id, target_id );
540     var svg = $('#svgenlargement').children('svg').svg().svg('get');
541     var path = svg.createPath(); 
542     var sx = parseInt( source_ellipse.attr('cx') );
543     var rx = parseInt( source_ellipse.attr('rx') );
544     var sy = parseInt( source_ellipse.attr('cy') );
545     var ex = parseInt( target_ellipse.attr('cx') );
546     var ey = parseInt( target_ellipse.attr('cy') );
547     var relation = svg.group( $("#svgenlargement svg g"), 
548         { 'class':'relation', 'id':relation_id } );
549     svg.title( relation, source_id + '->' + target_id );
550     svg.path( relation, path.move( sx, sy ).curveC( sx + (2*rx), sy, ex + (2*rx), ey, ex, ey ), {fill: 'none', stroke: relation_color, strokeWidth: 4});
551     var relation_element = $('#svgenlargement .relation').filter( ':last' );
552     relation_element.insertBefore( $('#svgenlargement g g').filter(':first') );
553     return relation_element;
554 }
555
556
557 $(document).ready(function () {
558     
559   timer = null;
560   relation_manager = new relation_factory();
561   $('#update_workspace_button').data('locked', false);
562   
563   $('#enlargement').mousedown(function (event) {
564     $(this)
565         .data('down', true)
566         .data('x', event.clientX)
567         .data('y', event.clientY)
568         .data('scrollLeft', this.scrollLeft)
569         stateTf = svg_root_element.getCTM().inverse();
570         var p = svg_root.createSVGPoint();
571         p.x = event.clientX;
572         p.y = event.clientY;
573         stateOrigin = p.matrixTransform(stateTf);
574         event.returnValue = false;
575         event.preventDefault();
576         return false;
577   }).mouseup(function (event) {
578         $(this).data('down', false);
579   }).mousemove(function (event) {
580     if( timer != null ) { clearTimeout(timer); } 
581     if ( ($(this).data('down') == true) && ($('#update_workspace_button').data('locked') == false) ) {
582         var p = svg_root.createSVGPoint();
583         p.x = event.clientX;
584         p.y = event.clientY;
585         p = p.matrixTransform(stateTf);
586         var matrix = stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y);
587         var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
588         svg_root_element.setAttribute("transform", s);
589     }
590     event.returnValue = false;
591     event.preventDefault();
592   }).mousewheel(function (event, delta) {
593     event.returnValue = false;
594     event.preventDefault();
595     if ( $('#update_workspace_button').data('locked') == false ) {
596         if (!delta || delta == null || delta == 0) delta = event.originalEvent.wheelDelta;
597         if (!delta || delta == null || delta == 0) delta = -1 * event.originalEvent.detail;
598         if( delta < -9 ) { delta = -9 }; 
599         var z = 1 + delta/10;
600         z = delta > 0 ? 1 : -1;
601         var g = svg_root_element;
602         if (g && ((z<1 && (g.getScreenCTM().a * start_element_height) > 4.0) || (z>=1 && (g.getScreenCTM().a * start_element_height) < 100))) {
603             var root = svg_root;
604             var p = root.createSVGPoint();
605             p.x = event.originalEvent.clientX;
606             p.y = event.originalEvent.clientY;
607             p = p.matrixTransform(g.getCTM().inverse());
608             var scaleLevel = 1+(z/20);
609             var k = root.createSVGMatrix().translate(p.x, p.y).scale(scaleLevel).translate(-p.x, -p.y);
610             var matrix = g.getCTM().multiply(k);
611             var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
612             g.setAttribute("transform", s);
613         }
614     }
615   }).css({
616     'overflow' : 'hidden',
617     'cursor' : '-moz-grab'
618   });
619   
620
621   $( "#dialog-form" ).dialog({
622     autoOpen: false,
623     height: 270,
624     width: 290,
625     modal: true,
626     buttons: {
627       "Ok": function( evt ) {
628         $(evt.target).button("disable");
629         $('#status').empty();
630         form_values = $('#collapse_node_form').serialize();
631         ncpath = getTextURL( 'relationships' );
632         var jqjson = $.post( ncpath, form_values, function(data) {
633             $.each( data, function(item, source_target) { 
634                 var source_found = get_ellipse( source_target[0] );
635                 var target_found = get_ellipse( source_target[1] );
636                 if( source_found.size() && target_found.size() ) {
637                     var relation = relation_manager.create( source_target[0], source_target[1], $('#rel_type')[0].selectedIndex-1 );
638                                         relation.data( 'type', $('#rel_type :selected').text()  );
639                                         relation.data( 'scope', $('#scope :selected').text()  );
640                                         relation.data( 'note', $('#note').val()  );
641                                         relation_manager.toggle_active( relation.attr('id') );
642                                 }
643                                 $(evt.target).button("enable");
644            });
645             $( "#dialog-form" ).dialog( "close" );
646         }, 'json' );
647       },
648       Cancel: function() {
649           $( this ).dialog( "close" );
650       }
651     },
652     create: function(event, ui) { 
653         $(this).data( 'relation_drawn', false );
654         //TODO? Err handling?
655         var jqjson = $.getJSON( basepath + 'definitions', function(data) {
656             var types = data.types.sort();
657             $.each( types, function(index, value) {   
658                  $('#rel_type').append( $('<option />').attr( "value", value ).text(value) ); 
659                  $('#keymaplist').append( $('<li>').css( "border-color", relation_manager.relation_colors[index] ).text(value) ); 
660             });
661             var scopes = data.scopes;
662             $.each( scopes, function(index, value) {   
663                  $('#scope').append( $('<option />').attr( "value", value ).text(value) ); 
664             });
665         });        
666     },
667     open: function() {
668         relation_manager.create_temporary( $('#source_node_id').val(), $('#target_node_id').val() );
669         $(".ui-widget-overlay").css("background", "none");
670         $("#dialog_overlay").show();
671         $("#dialog_overlay").height( $("#enlargement_container").height() );
672         $("#dialog_overlay").width( $("#enlargement_container").innerWidth() );
673         $("#dialog_overlay").offset( $("#enlargement_container").offset() );
674     },
675     close: function() {
676         relation_manager.remove_temporary();
677         $( '#status' ).empty();
678         $("#dialog_overlay").hide();
679     }
680   }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
681       if( ajaxSettings.url == getTextURL('relationships') 
682         && ajaxSettings.type == 'POST' && jqXHR.status == 403 ) {
683           var errobj = jQuery.parseJSON( jqXHR.responseText );
684           $('#status').append( '<p class="error">Error: ' + errobj.error + '</br>The relationship cannot be made.</p>' );
685       }
686           $(event.target).parent().find('.ui-button').button("enable");
687   } );
688
689   $( "#delete-form" ).dialog({
690     autoOpen: false,
691     height: 135,
692     width: 160,
693     modal: false,
694     buttons: {
695         Cancel: function() {
696             $( this ).dialog( "close" );
697         },
698         Delete: function() {
699           form_values = $('#delete_relation_form').serialize();
700           ncpath = getTextURL( 'relationships' );
701           var jqjson = $.ajax({ url: ncpath, data: form_values, success: function(data) {
702               $.each( data, function(item, source_target) { 
703                   relation_manager.remove( get_relation_id( source_target[0], source_target[1] ) );
704               });
705               $( "#delete-form" ).dialog( "close" );
706           }, dataType: 'json', type: 'DELETE' });
707         }
708     },
709     create: function(event, ui) {
710         var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ).css( 'width', '100%' );
711         buttonset.find( "button:contains('Cancel')" ).css( 'float', 'right' );
712         var dialog_aria = $("div[aria-labelledby='ui-dialog-title-delete-form']");  
713         dialog_aria.mouseenter( function() {
714             if( mouseWait != null ) { clearTimeout(mouseWait) };
715         })
716         dialog_aria.mouseleave( function() {
717             mouseWait = setTimeout( function() { $("#delete-form").dialog( "close" ) }, 2000 );
718         })
719     },
720     open: function() {
721         mouseWait = setTimeout( function() { $("#delete-form").dialog( "close" ) }, 2000 );
722     },
723     close: function() {
724     }
725   });
726
727   // function for reading form dialog should go here; for now hide the element
728   $('#reading-form').dialog({
729         autoOpen: false,
730         height: 400,
731         width: 600,
732         modal: true,
733         buttons: {
734                 Cancel: function() {
735                         $( this ).dialog( "close" );
736                 },
737                 Update: function( evt ) {
738                         // Disable the button
739                         $(evt.target).button("disable");
740                         $('#reading_status').empty();
741                         var reading_id = $('#reading_id').val()
742                         form_values = {
743                                 'id' : reading_id,
744                                 'is_nonsense': $('#reading_is_nonsense').is(':checked'),
745                                 'grammar_invalid': $('#reading_grammar_invalid').is(':checked'),
746                                 'normal_form': $('#reading_normal_form').val() };
747                         // Add the morphology values
748                         $('.reading_morphology').each( function() {
749                                 if( $(this).val() != '(Click to select)' ) {
750                                         var rmid = $(this).attr('id');
751                                         rmid = rmid.substring(8);
752                                         form_values[rmid] = $(this).val();
753                                 }
754                         });
755                         // Make the JSON call
756                         ncpath = getReadingURL( reading_id );
757                         var reading_element = readingdata[reading_id];
758                         // $(':button :contains("Update")').attr("disabled", true);
759                         var jqjson = $.post( ncpath, form_values, function(data) {
760                                 $.each( data, function(key, value) { 
761                                         reading_element[key] = value;
762                                 });
763                                 if( $('#update_workspace_button').data('locked') == false ) {
764                                         color_inactive( get_ellipse( reading_id ) );
765                                 }
766                                 $(evt.target).button("enable");
767                                 $( "#reading-form" ).dialog( "close" );
768                         });
769                         // Re-color the node if necessary
770                         return false;
771                 }
772         },
773         create: function() {
774         },
775         open: function() {
776         $(".ui-widget-overlay").css("background", "none");
777         $("#dialog_overlay").show();
778         $('#reading_status').empty();
779         $("#dialog_overlay").height( $("#enlargement_container").height() );
780         $("#dialog_overlay").width( $("#enlargement_container").innerWidth() );
781         $("#dialog_overlay").offset( $("#enlargement_container").offset() );
782         $("#reading-form").parent().find('.ui-button').button("enable");
783         },
784         close: function() {
785                 $("#dialog_overlay").hide();
786         }
787   }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
788       if( ajaxSettings.url.lastIndexOf( getReadingURL('') ) > -1
789         && ajaxSettings.type == 'POST' && jqXHR.status == 403 ) {
790           var errobj = jQuery.parseJSON( jqXHR.responseText );
791           $('#reading_status').append( '<p class="error">Error: ' + errobj.error + '</p>' );
792       }
793           $(event.target).parent().find('.ui-button').button("enable");
794   });
795   
796
797   $('#update_workspace_button').click( function() {
798      var svg_enlargement = $('#svgenlargement').svg().svg('get').root();
799      mouse_scale = svg_root_element.getScreenCTM().a;
800      if( $(this).data('locked') == true ) {
801          $('#svgenlargement ellipse' ).each( function( index ) {
802              if( $(this).data( 'node_obj' ) != null ) {
803                  $(this).data( 'node_obj' ).ungreyout_edges();
804                  $(this).data( 'node_obj' ).set_draggable( false );
805                  var node_id = $(this).data( 'node_obj' ).get_id();
806                  toggle_relation_active( node_id );
807                  $(this).data( 'node_obj', null );
808              }
809          })
810          $(this).data('locked', false);
811          $(this).css('background-position', '0px 44px');
812      } else {
813          var left = $('#enlargement').offset().left;
814          var right = left + $('#enlargement').width();
815          var tf = svg_root_element.getScreenCTM().inverse(); 
816          var p = svg_root.createSVGPoint();
817          p.x=left;
818          p.y=100;
819          var cx_min = p.matrixTransform(tf).x;
820          p.x=right;
821          var cx_max = p.matrixTransform(tf).x;
822          $('#svgenlargement ellipse').each( function( index ) {
823              var cx = parseInt( $(this).attr('cx') );
824              if( cx > cx_min && cx < cx_max) { 
825                  if( $(this).data( 'node_obj' ) == null ) {
826                      $(this).data( 'node_obj', new node_obj( $(this) ) );
827                  } else {
828                      $(this).data( 'node_obj' ).set_draggable( true );
829                  }
830                  $(this).data( 'node_obj' ).greyout_edges();
831                  var node_id = $(this).data( 'node_obj' ).get_id();
832                  toggle_relation_active( node_id );
833              }
834          });
835          $(this).css('background-position', '0px 0px');
836          $(this).data('locked', true );
837      }
838   });
839   
840   $('.helptag').popupWindow({ 
841           height:500, 
842           width:800, 
843           top:50, 
844           left:50,
845           scrollbars:1 
846   }); 
847
848   
849   function toggle_relation_active( node_id ) {
850       $('#svgenlargement .relation').find( "title:contains('" + node_id +  "')" ).each( function(index) {
851           matchid = new RegExp( "^" + node_id );
852           if( $(this).text().match( matchid ) != null ) {
853                   var relation_id = $(this).parent().attr('id');
854               relation_manager.toggle_active( relation_id );
855           };
856       });
857   }
858
859   expandFillPageClients();
860   $(window).resize(function() {
861     expandFillPageClients();
862   });
863
864 });
865
866
867 function expandFillPageClients() {
868         $('.fillPage').each(function () {
869                 $(this).height($(window).height() - $(this).offset().top - MARGIN);
870         });
871 }
872
873 function loadSVG(svgData) {
874         var svgElement = $('#svgenlargement');
875
876         $(svgElement).svg('destroy');
877
878         $(svgElement).svg({
879                 loadURL: svgData,
880                 onLoad : svgEnlargementLoaded
881         });
882 }
883
884
885 /*      OS Gadget stuff
886
887 function svg_select_callback(topic, data, subscriberData) {
888         svgData = data;
889         loadSVG(svgData);
890 }
891
892 function loaded() {
893         var prefs = new gadgets.Prefs();
894         var preferredHeight = parseInt(prefs.getString('height'));
895         if (gadgets.util.hasFeature('dynamic-height')) gadgets.window.adjustHeight(preferredHeight);
896         expandFillPageClients();
897 }
898
899 if (gadgets.util.hasFeature('pubsub-2')) {
900         gadgets.HubSettings.onConnect = function(hum, suc, err) {
901                 subId = gadgets.Hub.subscribe("interedition.svg.selected", svg_select_callback);
902                 loaded();
903         };
904 }
905 else gadgets.util.registerOnLoadHandler(loaded);
906 */