allow relationship colors in SVG download; work around inability to close Download...
[scpubgit/stemmaweb.git] / root / js / relationship.js
CommitLineData
76f05423 1var MARGIN=30;
2var svg_root = null;
3var svg_root_element = null;
4var start_element_height = 0;
5var reltypes = {};
5f15640c 6var readingdata = {};
76f05423 7
a471cb0f 8jQuery.removeFromArray = function(value, arr) {
9 return jQuery.grep(arr, function(elem, index) {
10 return elem !== value;
11 });
12};
13
a84ca4de 14function arrayUnique(array) {
15 var a = array.concat();
16 for(var i=0; i<a.length; ++i) {
17 for(var j=i+1; j<a.length; ++j) {
18 if(a[i] === a[j])
19 a.splice(j--, 1);
20 }
21 }
22 return a;
23};
24
5f15640c 25function getTextURL( which ) {
3f9d7ae5 26 return basepath + textid + '/' + which;
5f15640c 27}
28
29function getReadingURL( reading_id ) {
3f9d7ae5 30 return basepath + textid + '/reading/' + reading_id;
b28e606e 31}
32
45ee3b96 33// Make an XML ID into a valid selector
34function jq(myid) {
35 return '#' + myid.replace(/(:|\.)/g,'\\$1');
36}
37
065c7cf2 38// Actions for opening the reading panel
39function node_dblclick_listener( evt ) {
40 // Open the reading dialogue for the given node.
41 // First get the reading info
42 var reading_id = $(this).attr('id');
43 var reading_info = readingdata[reading_id];
44 // and then populate the dialog box with it.
45 // Set the easy properties first
46 $('#reading-form').dialog( 'option', 'title', 'Reading information for "' + reading_info['text'] + '"' );
47 $('#reading_id').val( reading_id );
798fa939 48 toggle_checkbox( $('#reading_is_nonsense'), reading_info['is_nonsense'] );
49 toggle_checkbox( $('#reading_grammar_invalid'), reading_info['grammar_invalid'] );
065c7cf2 50 // Use .text as a backup for .normal_form
51 var normal_form = reading_info['normal_form'];
52 if( !normal_form ) {
53 normal_form = reading_info['text'];
54 }
55 var nfboxsize = 10;
56 if( normal_form.length > 9 ) {
57 nfboxsize = normal_form.length + 1;
58 }
59 $('#reading_normal_form').attr( 'size', nfboxsize )
60 $('#reading_normal_form').val( normal_form );
61 // Now do the morphological properties.
0dcdd5ec 62 morphology_form( reading_info['lexemes'] );
065c7cf2 63 // and then open the dialog.
0dcdd5ec 64 $('#reading-form').dialog("open");
65}
66
798fa939 67function toggle_checkbox( box, value ) {
68 if( value == null ) {
69 value = false;
70 }
71 box.attr('checked', value );
72}
73
0dcdd5ec 74function morphology_form ( lexlist ) {
487674b9 75 if( lexlist.length ) {
76 $('#morph_outer').show();
77 $('#morphology').empty();
78 $.each( lexlist, function( idx, lex ) {
79 var morphoptions = [];
80 if( 'wordform_matchlist' in lex ) {
81 $.each( lex['wordform_matchlist'], function( tdx, tag ) {
82 var tagstr = stringify_wordform( tag );
83 morphoptions.push( tagstr );
84 });
85 }
86 var formtag = 'morphology_' + idx;
87 var formstr = '';
88 if( 'form' in lex ) {
89 formstr = stringify_wordform( lex['form'] );
90 }
91 var form_morph_elements = morph_elements(
92 formtag, lex['string'], formstr, morphoptions );
93 $.each( form_morph_elements, function( idx, el ) {
94 $('#morphology').append( el );
08f8443a 95 });
065c7cf2 96 });
487674b9 97 } else {
98 $('#morph_outer').hide();
99 }
065c7cf2 100}
101
102function stringify_wordform ( tag ) {
08f8443a 103 if( tag ) {
104 var elements = tag.split(' // ');
105 return elements[1] + ' // ' + elements[2];
106 }
107 return ''
065c7cf2 108}
109
d1132306 110function morph_elements ( formtag, formtxt, currform, morphoptions ) {
111 var clicktag = '(Click to select)';
112 if ( !currform ) {
113 currform = clicktag;
114 }
065c7cf2 115 var formlabel = $('<label/>').attr( 'id', 'label_' + formtag ).attr(
d1132306 116 'for', 'reading_' + formtag ).text( formtxt + ': ' );
065c7cf2 117 var forminput = $('<input/>').attr( 'id', 'reading_' + formtag ).attr(
0dcdd5ec 118 'name', 'reading_' + formtag ).attr( 'size', '50' ).attr(
119 'class', 'reading_morphology' ).val( currform );
d1132306 120 forminput.autocomplete({ source: morphoptions, minLength: 0 });
121 forminput.focus( function() {
122 if( $(this).val() == clicktag ) {
123 $(this).val('');
124 }
125 $(this).autocomplete('search', '')
126 });
065c7cf2 127 var morphel = [ formlabel, forminput, $('<br/>') ];
128 return morphel;
129}
130
4c41c02c 131function color_inactive ( el ) {
132 var reading_id = $(el).parent().attr('id');
133 var reading_info = readingdata[reading_id];
134 // If the reading info has any non-disambiguated lexemes, color it yellow;
135 // otherwise color it green.
136 $(el).attr( {stroke:'green', fill:'#b3f36d'} );
bb3230b1 137 if( reading_info ) {
138 $.each( reading_info['lexemes'], function ( idx, lex ) {
139 if( !lex['is_disambiguated'] || lex['is_disambiguated'] == 0 ) {
140 $(el).attr( {stroke:'orange', fill:'#fee233'} );
141 }
142 });
143 }
4c41c02c 144}
145
997ebe92 146function relemmatize () {
147 // Send the reading for a new lemmatization and reopen the form.
a0a66634 148 $('#relemmatize_pending').show();
997ebe92 149 var reading_id = $('#reading_id').val()
150 ncpath = getReadingURL( reading_id );
151 form_values = {
152 'normal_form': $('#reading_normal_form').val(),
153 'relemmatize': 1 };
154 var jqjson = $.post( ncpath, form_values, function( data ) {
155 // Update the form with the return
156 if( 'id' in data ) {
157 // We got back a good answer. Stash it
158 readingdata[reading_id] = data;
159 // and regenerate the morphology form.
160 morphology_form( data['lexemes'] );
161 } else {
162 alert("Could not relemmatize as requested: " + data['error']);
163 }
a0a66634 164 $('#relemmatize_pending').hide();
997ebe92 165 });
166}
167
065c7cf2 168// Initialize the SVG once it exists
b28e606e 169function svgEnlargementLoaded() {
fc018906 170 //Give some visual evidence that we are working
171 $('#loading_overlay').show();
172 lo_height = $("#enlargement_container").outerHeight();
173 lo_width = $("#enlargement_container").outerWidth();
174 $("#loading_overlay").height( lo_height );
175 $("#loading_overlay").width( lo_width );
176 $("#loading_overlay").offset( $("#enlargement_container").offset() );
177 $("#loading_message").offset(
178 { 'top': lo_height / 2 - $("#loading_message").height() / 2,
179 'left': lo_width / 2 - $("#loading_message").width() / 2 });
30d0ba1e 180 if( editable ) {
181 // Show the update toggle button.
182 $('#update_workspace_button').data('locked', false);
183 $('#update_workspace_button').css('background-position', '0px 44px');
184 }
065c7cf2 185 $('#svgenlargement ellipse').parent().dblclick( node_dblclick_listener );
9529f69c 186 var graph_svg = $('#svgenlargement svg');
187 var svg_g = $('#svgenlargement svg g')[0];
76f05423 188 if (!svg_g) return;
9529f69c 189 svg_root = graph_svg.svg().svg('get').root();
76f05423 190
191 // Find the real root and ignore any text nodes
192 for (i = 0; i < svg_root.childNodes.length; ++i) {
193 if (svg_root.childNodes[i].nodeName != '#text') {
194 svg_root_element = svg_root.childNodes[i];
195 break;
196 }
197 }
198
30d0ba1e 199 //Set viewbox width and height to width and height of $('#svgenlargement svg').
200 //This is essential to make sure zooming and panning works properly.
9529f69c 201 svg_root.viewBox.baseVal.width = graph_svg.attr( 'width' );
202 svg_root.viewBox.baseVal.height = graph_svg.attr( 'height' );
203 //Now set scale and translate so svg height is about 150px and vertically centered in viewbox.
204 //This is just to create a nice starting enlargement.
205 var initial_svg_height = 250;
206 var scale = initial_svg_height/graph_svg.attr( 'height' );
207 var additional_translate = (graph_svg.attr( 'height' ) - initial_svg_height)/(2*scale);
208 var transform = svg_g.getAttribute('transform');
209 var translate = parseFloat( transform.match( /translate\([^\)]*\)/ )[0].split('(')[1].split(' ')[1].split(')')[0] );
210 translate += additional_translate;
211 var transform = 'rotate(0) scale(' + scale + ') translate(4 ' + translate + ')';
212 svg_g.setAttribute('transform', transform);
213 //used to calculate min and max zoom level:
4c41c02c 214 start_element_height = $('#__START__').children('ellipse')[0].getBBox().height;
e538eccb 215 //some use of call backs to ensure succesive execution
216 add_relations( function() {
217 var rdgpath = getTextURL( 'readings' );
218 $.getJSON( rdgpath, function( data ) {
219 readingdata = data;
220 $('#svgenlargement ellipse').each( function( i, el ) { color_inactive( el ) });
9231663d 221 $('#loading_overlay').hide();
e538eccb 222 });
e538eccb 223 });
c1add777 224
225 //initialize marquee
226 marquee = new Marquee();
227
6afcd813 228}
229
fc018906 230function add_relations( callback_fn ) {
56e3972e 231 // Add the relationship types to the keymap list
56e3972e 232 $.each( relationship_types, function(index, typedef) {
671c04b1 233 li_elm = $('<li class="key">').css( "border-color",
234 relation_manager.relation_colors[index] ).text(typedef.name);
235 li_elm.append( $('<div>').attr('class', 'key_tip_container').append(
236 $('<div>').attr('class', 'key_tip').text(typedef.description) ) );
cfefd283 237 $('#keymaplist').append( li_elm );
56e3972e 238 });
239 // Now fetch the relationships themselves and add them to the graph
240 var rel_types = $.map( relationship_types, function(t) { return t.name });
241 // Save this list of names to the outer element data so that the relationship
242 // factory can access it
243 $('#keymap').data('relations', rel_types);
5f15640c 244 var textrelpath = getTextURL( 'relationships' );
56e3972e 245 $.getJSON( textrelpath, function(data) {
246 $.each(data, function( index, rel_info ) {
247 var type_index = $.inArray(rel_info.type, rel_types);
51ca18ee 248 var source_found = get_ellipse( rel_info.source_id );
249 var target_found = get_ellipse( rel_info.target_id );
56e3972e 250 if( type_index != -1 && source_found.size() && target_found.size() ) {
51ca18ee 251 var relation = relation_manager.create( rel_info.source_id, rel_info.target_id, type_index );
252 // Save the relationship data too.
253 $.each( rel_info, function( k, v ) {
254 relation.data( k, v );
255 });
30d0ba1e 256 if( editable ) {
51ca18ee 257 var node_obj = get_node_obj(rel_info.source_id);
e538eccb 258 node_obj.set_selectable( false );
30d0ba1e 259 node_obj.ellipse.data( 'node_obj', null );
51ca18ee 260 node_obj = get_node_obj(rel_info.target_id);
e538eccb 261 node_obj.set_selectable( false );
30d0ba1e 262 node_obj.ellipse.data( 'node_obj', null );
263 }
56e3972e 264 }
265 });
266 callback_fn.call();
267 });
b28e606e 268}
269
270function get_ellipse( node_id ) {
45ee3b96 271 return $( jq( node_id ) + ' ellipse');
b28e606e 272}
273
274function get_node_obj( node_id ) {
9529f69c 275 var node_ellipse = get_ellipse( node_id );
276 if( node_ellipse.data( 'node_obj' ) == null ) {
277 node_ellipse.data( 'node_obj', new node_obj(node_ellipse) );
278 };
279 return node_ellipse.data( 'node_obj' );
b28e606e 280}
281
b28e606e 282function node_obj(ellipse) {
283 this.ellipse = ellipse;
284 var self = this;
285
286 this.x = 0;
287 this.y = 0;
288 this.dx = 0;
289 this.dy = 0;
290 this.node_elements = node_elements_for(self.ellipse);
22a70299 291
b28e606e 292 this.get_id = function() {
45ee3b96 293 return $(self.ellipse).parent().attr('id')
b28e606e 294 }
295
e538eccb 296 this.set_selectable = function( clickable ) {
297 if( clickable && editable ) {
298 $(self.ellipse).attr( {stroke:'black', fill:'#fff'} );
299 $(self.ellipse).parent().hover( this.enter_node, this.leave_node );
300 $(self.ellipse).parent().mousedown( function(evt) { evt.stopPropagation() } );
301 $(self.ellipse).parent().click( function(evt) {
302 evt.stopPropagation();
303 if( $('ellipse[fill="#9999ff"]').size() > 0 ) {
304 $('ellipse[fill="#9999ff"]').each( function() {
305 $(this).data( 'node_obj' ).set_draggable( false );
306 } );
307 }
308 self.set_draggable( true )
309 });
310 } else {
311 $(self.ellipse).attr( {stroke:'black', fill:'#fff'} );
312 self.ellipse.siblings('text').attr('class', '');
313 $(self.ellipse).parent().unbind();
314 $('body').unbind('mousemove');
315 $('body').unbind('mouseup');
316 }
317 }
318
b28e606e 319 this.set_draggable = function( draggable ) {
30d0ba1e 320 if( draggable && editable ) {
e538eccb 321 $(self.ellipse).attr( {stroke:'black', fill:'#9999ff'} );
05485bfd 322 $(self.ellipse).parent().mousedown( this.mousedown_listener );
e538eccb 323 $(self.ellipse).parent().unbind( 'mouseenter' ).unbind( 'mouseleave' );
05485bfd 324 self.ellipse.siblings('text').attr('class', 'noselect draggable');
b28e606e 325 } else {
e538eccb 326 $(self.ellipse).attr( {stroke:'black', fill:'#fff'} );
05485bfd 327 self.ellipse.siblings('text').attr('class', '');
e538eccb 328 $(self.ellipse).parent().unbind( 'mousedown ');
329 $(self.ellipse).parent().mousedown( function(evt) { evt.stopPropagation() } );
330 $(self.ellipse).parent().hover( this.enter_node, this.leave_node );
b28e606e 331 }
332 }
333
334 this.mousedown_listener = function(evt) {
335 evt.stopPropagation();
336 self.x = evt.clientX;
337 self.y = evt.clientY;
338 $('body').mousemove( self.mousemove_listener );
339 $('body').mouseup( self.mouseup_listener );
05485bfd 340 $(self.ellipse).parent().unbind('mouseenter').unbind('mouseleave')
e538eccb 341 self.ellipse.attr( 'fill', '#6b6bb2' );
b28e606e 342 first_node_g_element = $("#svgenlargement g .node" ).filter( ":first" );
343 if( first_node_g_element.attr('id') !== self.get_g().attr('id') ) { self.get_g().insertBefore( first_node_g_element ) };
344 }
345
346 this.mousemove_listener = function(evt) {
9529f69c 347 self.dx = (evt.clientX - self.x) / mouse_scale;
348 self.dy = (evt.clientY - self.y) / mouse_scale;
b28e606e 349 self.move_elements();
05485bfd 350 evt.returnValue = false;
351 evt.preventDefault();
352 return false;
b28e606e 353 }
354
355 this.mouseup_listener = function(evt) {
356 if( $('ellipse[fill="#ffccff"]').size() > 0 ) {
45ee3b96 357 var source_node_id = $(self.ellipse).parent().attr('id');
05485bfd 358 var source_node_text = self.ellipse.siblings('text').text();
45ee3b96 359 var target_node_id = $('ellipse[fill="#ffccff"]').parent().attr('id');
05485bfd 360 var target_node_text = $('ellipse[fill="#ffccff"]').siblings("text").text();
b28e606e 361 $('#source_node_id').val( source_node_id );
05485bfd 362 $('#source_node_text').val( source_node_text );
51ca18ee 363 $('.rel_rdg_a').text( "'" + source_node_text + "'" );
b28e606e 364 $('#target_node_id').val( target_node_id );
05485bfd 365 $('#target_node_text').val( target_node_text );
51ca18ee 366 $('.rel_rdg_b').text( "'" + target_node_text + "'" );
b28e606e 367 $('#dialog-form').dialog( 'open' );
368 };
369 $('body').unbind('mousemove');
370 $('body').unbind('mouseup');
e538eccb 371 self.ellipse.attr( 'fill', '#9999ff' );
9529f69c 372 self.reset_elements();
b28e606e 373 }
f2fb96fc 374
b28e606e 375 this.cpos = function() {
376 return { x: self.ellipse.attr('cx'), y: self.ellipse.attr('cy') };
377 }
378
379 this.get_g = function() {
380 return self.ellipse.parent('g');
381 }
382
383 this.enter_node = function(evt) {
384 self.ellipse.attr( 'fill', '#ffccff' );
385 }
386
387 this.leave_node = function(evt) {
388 self.ellipse.attr( 'fill', '#fff' );
389 }
390
391 this.greyout_edges = function() {
392 $.each( self.node_elements, function(index, value) {
393 value.grey_out('.edge');
394 });
395 }
396
397 this.ungreyout_edges = function() {
398 $.each( self.node_elements, function(index, value) {
399 value.un_grey_out('.edge');
400 });
401 }
402
6c8d1880 403 this.reposition = function( dx, dy ) {
404 $.each( self.node_elements, function(index, value) {
405 value.reposition( dx, dy );
406 } );
407 }
408
b28e606e 409 this.move_elements = function() {
410 $.each( self.node_elements, function(index, value) {
6c8d1880 411 value.move( self.dx, self.dy );
22a70299 412 } );
b28e606e 413 }
414
415 this.reset_elements = function() {
416 $.each( self.node_elements, function(index, value) {
6c8d1880 417 value.reset();
22a70299 418 } );
b28e606e 419 }
420
421 this.update_elements = function() {
422 self.node_elements = node_elements_for(self.ellipse);
423 }
424
a84ca4de 425 this.get_witnesses = function() {
426 return readingdata[self.get_id()].witnesses
427 }
6c8d1880 428
e538eccb 429 self.set_selectable( true );
b28e606e 430}
431
432function svgshape( shape_element ) {
433 this.shape = shape_element;
6c8d1880 434 this.reposx = 0;
435 this.reposy = 0;
436 this.repositioned = this.shape.parent().data( 'repositioned' );
437 if( this.repositioned != null ) {
438 this.reposx = this.repositioned[0];
439 this.reposy = this.repositioned[1];
440 }
441 this.reposition = function (dx, dy) {
442 this.move( dx, dy );
443 this.reposx = this.reposx + dx;
444 this.reposy = this.reposy + dy;
445 this.shape.parent().data( 'repositioned', [this.reposx,this.reposy] );
446 }
b28e606e 447 this.move = function(dx,dy) {
6c8d1880 448 this.shape.attr( "transform", "translate( " + (this.reposx + dx) + " " + (this.reposy + dy) + " )" );
b28e606e 449 }
450 this.reset = function() {
6c8d1880 451 this.shape.attr( "transform", "translate( " + this.reposx + " " + this.reposy + " )" );
b28e606e 452 }
453 this.grey_out = function(filter) {
454 if( this.shape.parent(filter).size() != 0 ) {
455 this.shape.attr({'stroke':'#e5e5e5', 'fill':'#e5e5e5'});
456 }
457 }
458 this.un_grey_out = function(filter) {
459 if( this.shape.parent(filter).size() != 0 ) {
460 this.shape.attr({'stroke':'#000000', 'fill':'#000000'});
461 }
462 }
463}
464
465function svgpath( path_element, svg_element ) {
466 this.svg_element = svg_element;
467 this.path = path_element;
468 this.x = this.path.x;
469 this.y = this.path.y;
6c8d1880 470
471 this.reposition = function (dx, dy) {
472 this.x = this.x + dx;
473 this.y = this.y + dy;
474 this.path.x = this.x;
475 this.path.y = this.y;
476 }
477
b28e606e 478 this.move = function(dx,dy) {
479 this.path.x = this.x + dx;
480 this.path.y = this.y + dy;
481 }
6c8d1880 482
b28e606e 483 this.reset = function() {
484 this.path.x = this.x;
485 this.path.y = this.y;
486 }
6c8d1880 487
b28e606e 488 this.grey_out = function(filter) {
489 if( this.svg_element.parent(filter).size() != 0 ) {
490 this.svg_element.attr('stroke', '#e5e5e5');
491 this.svg_element.siblings('text').attr('fill', '#e5e5e5');
05485bfd 492 this.svg_element.siblings('text').attr('class', 'noselect');
b28e606e 493 }
494 }
495 this.un_grey_out = function(filter) {
496 if( this.svg_element.parent(filter).size() != 0 ) {
497 this.svg_element.attr('stroke', '#000000');
498 this.svg_element.siblings('text').attr('fill', '#000000');
05485bfd 499 this.svg_element.siblings('text').attr('class', '');
b28e606e 500 }
501 }
502}
503
504function node_elements_for( ellipse ) {
505 node_elements = get_edge_elements_for( ellipse );
506 node_elements.push( new svgshape( ellipse.siblings('text') ) );
507 node_elements.push( new svgshape( ellipse ) );
508 return node_elements;
509}
510
511function get_edge_elements_for( ellipse ) {
512 edge_elements = new Array();
45ee3b96 513 node_id = ellipse.parent().attr('id');
b28e606e 514 edge_in_pattern = new RegExp( node_id + '$' );
22a70299 515 edge_out_pattern = new RegExp( '^' + node_id + '-' );
b28e606e 516 $.each( $('#svgenlargement .edge,#svgenlargement .relation').children('title'), function(index) {
517 title = $(this).text();
518 if( edge_in_pattern.test(title) ) {
519 polygon = $(this).siblings('polygon');
520 if( polygon.size() > 0 ) {
521 edge_elements.push( new svgshape( polygon ) );
522 }
523 path_segments = $(this).siblings('path')[0].pathSegList;
524 edge_elements.push( new svgpath( path_segments.getItem(path_segments.numberOfItems - 1), $(this).siblings('path') ) );
525 }
526 if( edge_out_pattern.test(title) ) {
527 path_segments = $(this).siblings('path')[0].pathSegList;
528 edge_elements.push( new svgpath( path_segments.getItem(0), $(this).siblings('path') ) );
529 }
530 });
531 return edge_elements;
532}
533
534function relation_factory() {
535 var self = this;
536 this.color_memo = null;
537 //TODO: colors hard coded for now
538 this.temp_color = '#FFA14F';
539 this.relation_colors = [ "#5CCCCC", "#67E667", "#F9FE72", "#6B90D4", "#FF7673", "#E467B3", "#AA67D5", "#8370D8", "#FFC173" ];
540
541 this.create_temporary = function( source_node_id, target_node_id ) {
45ee3b96 542 var relation_id = get_relation_id( source_node_id, target_node_id );
543 var relation = $( jq( relation_id ) );
9529f69c 544 if( relation.size() == 0 ) {
b28e606e 545 draw_relation( source_node_id, target_node_id, self.temp_color );
546 } else {
547 self.color_memo = relation.children('path').attr( 'stroke' );
548 relation.children('path').attr( 'stroke', self.temp_color );
549 }
550 }
551 this.remove_temporary = function() {
552 var path_element = $('#svgenlargement .relation').children('path[stroke="' + self.temp_color + '"]');
553 if( self.color_memo != null ) {
554 path_element.attr( 'stroke', self.color_memo );
555 self.color_memo = null;
556 } else {
9529f69c 557 var temporary = path_element.parent('g').remove();
558 temporary.empty();
559 temporary = null;
b28e606e 560 }
561 }
562 this.create = function( source_node_id, target_node_id, color_index ) {
563 //TODO: Protect from (color_)index out of bound..
564 var relation_color = self.relation_colors[ color_index ];
9529f69c 565 var relation = draw_relation( source_node_id, target_node_id, relation_color );
566 get_node_obj( source_node_id ).update_elements();
567 get_node_obj( target_node_id ).update_elements();
568 return relation;
b28e606e 569 }
9529f69c 570 this.toggle_active = function( relation_id ) {
45ee3b96 571 var relation = $( jq( relation_id ) );
9529f69c 572 var relation_path = relation.children('path');
573 if( !relation.data( 'active' ) ) {
574 relation_path.css( {'cursor':'pointer'} );
575 relation_path.mouseenter( function(event) {
576 outerTimer = setTimeout( function() {
577 timer = setTimeout( function() {
45ee3b96 578 var related_nodes = get_related_nodes( relation_id );
579 var source_node_id = related_nodes[0];
580 var target_node_id = related_nodes[1];
9529f69c 581 $('#delete_source_node_id').val( source_node_id );
582 $('#delete_target_node_id').val( target_node_id );
583 self.showinfo(relation);
584 }, 500 )
585 }, 1000 );
586 });
587 relation_path.mouseleave( function(event) {
588 clearTimeout(outerTimer);
589 if( timer != null ) { clearTimeout(timer); }
590 });
591 relation.data( 'active', true );
592 } else {
593 relation_path.unbind( 'mouseenter' );
594 relation_path.unbind( 'mouseleave' );
595 relation_path.css( {'cursor':'inherit'} );
596 relation.data( 'active', false );
597 }
598 }
599 this.showinfo = function(relation) {
088a14af 600 $('#delete_relation_type').text( relation.data('type') );
601 $('#delete_relation_scope').text( relation.data('scope') );
51ca18ee 602 $('#delete_relation_attributes').empty();
603 if( relation.data( 'a_derivable_from_b' ) ) {
604 $('#delete_relation_attributes').append(
605 "'" + relation.data('source_text') + "' derivable from '" + relation.data('target_text') + "'<br/>");
606 }
607 if( relation.data( 'b_derivable_from_a' ) ) {
608 $('#delete_relation_attributes').append(
609 "'" + relation.data('target_text') + "' derivable from '" + relation.data('source_text') + "'<br/>");
610 }
611 if( relation.data( 'non_independent' ) ) {
612 $('#delete_relation_attributes').append(
613 "Variance unlikely to arise coincidentally<br/>");
614 }
69a19c91 615 if( relation.data( 'note' ) ) {
088a14af 616 $('#delete_relation_note').text('note: ' + relation.data( 'note' ) );
69a19c91 617 }
9529f69c 618 var points = relation.children('path').attr('d').slice(1).replace('C',' ').split(' ');
619 var xs = parseFloat( points[0].split(',')[0] );
620 var xe = parseFloat( points[1].split(',')[0] );
621 var ys = parseFloat( points[0].split(',')[1] );
622 var ye = parseFloat( points[3].split(',')[1] );
623 var p = svg_root.createSVGPoint();
624 p.x = xs + ((xe-xs)*1.1);
625 p.y = ye - ((ye-ys)/2);
76f05423 626 var ctm = svg_root_element.getScreenCTM();
9529f69c 627 var nx = p.matrixTransform(ctm).x;
628 var ny = p.matrixTransform(ctm).y;
629 var dialog_aria = $ ("div[aria-labelledby='ui-dialog-title-delete-form']");
630 $('#delete-form').dialog( 'open' );
631 dialog_aria.offset({ left: nx, top: ny });
632 }
633 this.remove = function( relation_id ) {
30d0ba1e 634 if( !editable ) {
635 return;
636 }
45ee3b96 637 var relation = $( jq( relation_id ) );
9529f69c 638 relation.remove();
b28e606e 639 }
640}
641
45ee3b96 642// Utility function to create/return the ID of a relation link between
643// a source and target.
644function get_relation_id( source_id, target_id ) {
645 var idlist = [ source_id, target_id ];
646 idlist.sort();
647 return 'relation-' + idlist[0] + '-...-' + idlist[1];
648}
649
650function get_related_nodes( relation_id ) {
651 var srctotarg = relation_id.substr( 9 );
652 return srctotarg.split('-...-');
653}
654
b28e606e 655function draw_relation( source_id, target_id, relation_color ) {
9529f69c 656 var source_ellipse = get_ellipse( source_id );
657 var target_ellipse = get_ellipse( target_id );
45ee3b96 658 var relation_id = get_relation_id( source_id, target_id );
9529f69c 659 var svg = $('#svgenlargement').children('svg').svg().svg('get');
660 var path = svg.createPath();
661 var sx = parseInt( source_ellipse.attr('cx') );
662 var rx = parseInt( source_ellipse.attr('rx') );
663 var sy = parseInt( source_ellipse.attr('cy') );
664 var ex = parseInt( target_ellipse.attr('cx') );
665 var ey = parseInt( target_ellipse.attr('cy') );
9231663d 666 var relation = svg.group( $("#svgenlargement svg g"), { 'class':'relation', 'id':relation_id } );
9529f69c 667 svg.title( relation, source_id + '->' + target_id );
668 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});
b28e606e 669 var relation_element = $('#svgenlargement .relation').filter( ':last' );
670 relation_element.insertBefore( $('#svgenlargement g g').filter(':first') );
9529f69c 671 return relation_element;
b28e606e 672}
673
5fe117bc 674function detach_node( readings ) {
217f5e64 675 // separate out the deleted relationships, discard for now
b6f3476b 676 if( 'DELETED' in readings ) {
677 // Remove each of the deleted relationship links.
678 $.each( readings['DELETED'], function( idx, pair ) {
679 var relation_id = get_relation_id( pair[0], pair[1] );
680 var relation = $( jq( relation_id ) );
681 if( relation.size() == 0 ) {
682 relation_id = get_relation_id( pair[1], pair[0] );
683 }
684 relation_manager.remove( relation_id );
685 });
686 delete readings['DELETED'];
687 }
a471cb0f 688 // add new node(s)
689 $.extend( readingdata, readings );
690 // remove from existing readings the witnesses for the new nodes/readings
691 $.each( readings, function( node_id, reading ) {
692 $.each( reading.witnesses, function( index, witness ) {
693 var witnesses = readingdata[ reading.orig_rdg ].witnesses;
694 readingdata[ reading.orig_rdg ].witnesses = $.removeFromArray( witness, witnesses );
695 } );
696 } );
697
a471cb0f 698 detached_edges = [];
8cd2e785 699
700 // here we detach witnesses from the existing edges accoring to what's being relayed by readings
a471cb0f 701 $.each( readings, function( node_id, reading ) {
702 var edges = edges_of( get_ellipse( reading.orig_rdg ) );
703 incoming_remaining = [];
704 outgoing_remaining = [];
705 $.each( reading.witnesses, function( index, witness ) {
706 incoming_remaining.push( witness );
707 outgoing_remaining.push( witness );
708 } );
709 $.each( edges, function( index, edge ) {
710 detached_edge = edge.detach_witnesses( reading.witnesses );
711 if( detached_edge != null ) {
712 detached_edges.push( detached_edge );
713 $.each( detached_edge.witnesses, function( index, witness ) {
714 if( detached_edge.is_incoming == true ) {
715 incoming_remaining = $.removeFromArray( witness, incoming_remaining );
716 } else {
717 outgoing_remaining = $.removeFromArray( witness, outgoing_remaining );
718 }
719 } );
720 }
721 } );
8cd2e785 722
60c66cd2 723 // After detaching we still need to check if for *all* readings
8cd2e785 724 // an edge was detached. It may be that a witness was not
725 // explicitly named on an edge but was part of a 'majority' edge
726 // in which case we need to duplicate and name that edge after those
727 // remaining witnesses.
a471cb0f 728 if( outgoing_remaining.length > 0 ) {
a471cb0f 729 $.each( edges, function( index, edge ) {
a471cb0f 730 if( edge.get_label() == 'majority' && !edge.is_incoming ) {
731 detached_edges.push( edge.clone_for( outgoing_remaining ) );
732 }
733 } );
734 }
735 if( incoming_remaining.length > 0 ) {
a471cb0f 736 $.each( edges, function( index, edge ) {
737 if( edge.get_label() == 'majority' && edge.is_incoming ) {
dc8a13bd 738 detached_edges.push( edge.clone_for( incoming_remaining ) );
a471cb0f 739 }
740 } );
741 }
60c66cd2 742
22a70299 743 // Finally multiple selected nodes may share edges
744 var copy_array = [];
745 $.each( detached_edges, function( index, edge ) {
746 var do_copy = true;
747 $.each( copy_array, function( index, copy_edge ) {
748 if( copy_edge.g_elem.attr( 'id' ) == edge.g_elem.attr( 'id' ) ) { do_copy = false }
749 } );
750 if( do_copy == true ) {
751 copy_array.push( edge );
752 }
753 } );
754 detached_edges = copy_array;
755
60c66cd2 756 // Lots of unabstracted knowledge down here :/
757 // Clone original node/reading, rename/id it..
758 duplicate_node = get_ellipse( reading.orig_rdg ).parent().clone();
759 duplicate_node.attr( 'id', node_id );
760 duplicate_node.children( 'title' ).text( node_id );
6c8d1880 761
762 // This needs somehow to move to node or even to shapes! #repositioned
763 duplicate_node_data = get_ellipse( reading.orig_rdg ).parent().data( 'repositioned' );
22a70299 764 if( duplicate_node_data != null ) {
6c8d1880 765 duplicate_node.children( 'ellipse' ).parent().data( 'repositioned', duplicate_node_data );
22a70299 766 }
60c66cd2 767
768 // Add the node and all new edges into the graph
931ed236 769 var graph_root = $('#svgenlargement svg g.graph');
60c66cd2 770 graph_root.append( duplicate_node );
771 $.each( detached_edges, function( index, edge ) {
dc8a13bd 772 id_suffix = node_id.slice( node_id.indexOf( '_' ) );
773 edge.g_elem.attr( 'id', ( edge.g_elem.attr( 'id' ) + id_suffix ) );
60c66cd2 774 edge_title = edge.g_elem.children( 'title' ).text();
22a70299 775 edge_weight = 0.8 + ( 0.2 * edge.witnesses.length );
60c66cd2 776 edge_title = edge_title.replace( reading.orig_rdg, node_id );
777 edge.g_elem.children( 'title' ).text( edge_title );
22a70299 778 edge.g_elem.children( 'path').attr( 'stroke-width', edge_weight );
60c66cd2 779 // Reg unabstracted knowledge: isn't it more elegant to make
780 // it edge.append_to( graph_root )?
781 graph_root.append( edge.g_elem );
782 } );
22a70299 783
784 // Make the detached node a real node_obj
785 var ellipse_elem = get_ellipse( node_id );
786 var new_node = new node_obj( ellipse_elem );
787 ellipse_elem.data( 'node_obj', new_node );
788
60c66cd2 789 // Move the node somewhat up for 'dramatic effect' :-p
9231663d 790 new_node.reposition( 0, -70 );
60c66cd2 791
a471cb0f 792 } );
60c66cd2 793
9231663d 794}
a471cb0f 795
9231663d 796function merge_nodes( source_node_id, target_node_id, consequences ) {
797 if( consequences.status != null && consequences.status == 'ok' ) {
798 merge_node( source_node_id, target_node_id );
799 if( consequences.checkalign != null ) {
800 $.each( consequences.checkalign, function( index, node_ids ) {
0c4354f6 801 var temp_relation = draw_relation( node_ids[0], node_ids[1], "#89a02c" );
9231663d 802 var sy = parseInt( temp_relation.children('path').attr('d').split('C')[0].split(',')[1] );
803 var ey = parseInt( temp_relation.children('path').attr('d').split(' ')[2].split(',')[1] );
804 var yC = ey + (( sy - ey )/2);
805 // TODO: compute xC to be always the same distance to the amplitude of the curve
806 var xC = parseInt( temp_relation.children('path').attr('d').split(' ')[1].split(',')[0] );
807 var svg = $('#svgenlargement').children('svg').svg('get');
808 parent_g = svg.group( $('#svgenlargement svg g') );
809 var ids_text = node_ids[0] + '-' + node_ids[1];
810 var merge_id = 'merge-' + ids_text;
48156ccd 811 svg.image( parent_g, xC, (yC-8), 16, 16, merge_button_yes, { id: merge_id } );
812 svg.image( parent_g, (xC+20), (yC-8), 16, 16, merge_button_no, { id: 'no' + merge_id } );
9231663d 813 $( '#' + merge_id ).hover( function(){ $(this).addClass( 'draggable' ) }, function(){ $(this).removeClass( 'draggable' ) } );
814 $( '#no' + merge_id ).hover( function(){ $(this).addClass( 'draggable' ) }, function(){ $(this).removeClass( 'draggable' ) } );
815 $( '#' + merge_id ).click( function( evt ){
816 merge_node( node_ids[0], node_ids[1] );
817 temp_relation.remove();
818 $( '#' + merge_id ).parent().remove();
759f1868 819 //notify backend
820 var ncpath = getTextURL( 'merge' );
aaf27b2e 821 var form_values = "source_id=" + node_ids[0] + "&target_id=" + node_ids[1] + "&single=true";
759f1868 822 $.post( ncpath, form_values );
9231663d 823 } );
824 $( '#no' + merge_id ).click( function( evt ) {
825 temp_relation.remove();
826 $( '#' + merge_id ).parent().remove();
827 } );
828 } );
829 }
830 }
831}
832
833function merge_node( source_node_id, target_node_id ) {
834 $.each( edges_of( get_ellipse( source_node_id ) ), function( index, edge ) {
835 if( edge.is_incoming == true ) {
836 edge.attach_endpoint( target_node_id );
837 } else {
838 edge.attach_startpoint( target_node_id );
839 }
840 } );
841 $( jq( source_node_id ) ).remove();
a471cb0f 842}
843
c1add777 844function Marquee() {
845
846 var self = this;
847
f6516f22 848 this.x = 0;
849 this.y = 0;
850 this.dx = 0;
851 this.dy = 0;
c1add777 852 this.enlargementOffset = $('#svgenlargement').offset();
853 this.svg_rect = $('#svgenlargement svg').svg('get');
854
855 this.show = function( event ) {
f6516f22 856 self.x = event.clientX;
857 self.y = event.clientY;
c1add777 858 p = svg_root.createSVGPoint();
859 p.x = event.clientX - self.enlargementOffset.left;
860 p.y = event.clientY - self.enlargementOffset.top;
c1add777 861 self.svg_rect.rect( p.x, p.y, 0, 0, { fill: 'black', 'fill-opacity': '0.1', stroke: 'black', 'stroke-dasharray': '4,2', strokeWidth: '0.02em', id: 'marquee' } );
862 };
863
864 this.expand = function( event ) {
f6516f22 865 self.dx = (event.clientX - self.x);
866 self.dy = (event.clientY - self.y);
c1add777 867 var rect = $('#marquee');
f6516f22 868 if( rect.length != 0 ) {
869 var rect_w = Math.abs( self.dx );
870 var rect_h = Math.abs( self.dy );
871 var rect_x = self.x - self.enlargementOffset.left;
872 var rect_y = self.y - self.enlargementOffset.top;
873 if( self.dx < 0 ) { rect_x = rect_x - rect_w }
874 if( self.dy < 0 ) { rect_y = rect_y - rect_h }
875 rect.attr("x", rect_x).attr("y", rect_y).attr("width", rect_w).attr("height", rect_h);
c1add777 876 }
877 };
878
a84ca4de 879 this.select = function() {
c1add777 880 var rect = $('#marquee');
881 if( rect.length != 0 ) {
e538eccb 882 //unselect any possible selected first
22a70299 883 //TODO: unless SHIFT?
e538eccb 884 if( $('ellipse[fill="#9999ff"]').size() > 0 ) {
885 $('ellipse[fill="#9999ff"]').each( function() {
886 $(this).data( 'node_obj' ).set_draggable( false );
887 } );
888 }
889 //compute dimension of marquee
c1add777 890 var left = $('#marquee').offset().left;
891 var top = $('#marquee').offset().top;
892 var right = left + parseInt( $('#marquee').attr( 'width' ) );
893 var bottom = top + parseInt( $('#marquee').attr( 'height' ) );
894 var tf = svg_root_element.getScreenCTM().inverse();
895 var p = svg_root.createSVGPoint();
896 p.x=left;
897 p.y=top;
898 var cx_min = p.matrixTransform(tf).x;
899 var cy_min = p.matrixTransform(tf).y;
900 p.x=right;
901 p.y=bottom;
902 var cx_max = p.matrixTransform(tf).x;
903 var cy_max = p.matrixTransform(tf).y;
e538eccb 904 //select any node with its center inside the marquee
5fe117bc 905 var readings = [];
e538eccb 906 //also merge witness sets from nodes
a84ca4de 907 var witnesses = [];
c1add777 908 $('#svgenlargement ellipse').each( function( index ) {
909 var cx = parseInt( $(this).attr('cx') );
910 var cy = parseInt( $(this).attr('cy') );
6c8d1880 911
912 // This needs somehow to move to node or even to shapes! #repositioned
913 // We should ask something more aling the lines of: nodes.each { |item| node.selected? }
914 var org_translate = $(this).parent().data( 'repositioned' );
22a70299 915 if( org_translate != null ) {
916 cx = cx + org_translate[0];
917 cy = cy + org_translate[1];
918 }
6c8d1880 919
c1add777 920 if( cx > cx_min && cx < cx_max) {
921 if( cy > cy_min && cy < cy_max) {
922 // we actually heve no real 'selected' state for nodes, except coloring
e538eccb 923 $(this).attr( 'fill', '#9999ff' );
5fe117bc 924 // Take note of the selected reading(s) and applicable witness(es)
925 // so we can populate the multipleselect-form
926 readings.push( $(this).parent().attr('id') );
a84ca4de 927 var this_witnesses = $(this).data( 'node_obj' ).get_witnesses();
928 witnesses = arrayUnique( witnesses.concat( this_witnesses ) );
c1add777 929 }
930 }
931 });
e538eccb 932 if( $('ellipse[fill="#9999ff"]').size() > 0 ) {
5fe117bc 933 //add intersection of witnesses sets to the multi select form and open it
934 $('#detach_collated_form').empty();
935 $.each( readings, function( index, value ) {
936 $('#detach_collated_form').append( $('<input>').attr(
937 "type", "hidden").attr("name", "readings[]").attr(
938 "value", value ) );
939 });
940 $.each( witnesses, function( index, value ) {
941 $('#detach_collated_form').append(
942 '<input type="checkbox" name="witnesses[]" value="' + value
943 + '">' + value + '<br>' );
a84ca4de 944 });
5fe117bc 945 $('#multiple_selected_readings').attr('value', readings.join(',') );
a84ca4de 946 $('#multipleselect-form').dialog( 'open' );
947 }
c1add777 948 self.svg_rect.remove( $('#marquee') );
949 }
950 };
951
f6516f22 952 this.unselect = function() {
e538eccb 953 $('ellipse[fill="#9999ff"]').attr( 'fill', '#fff' );
f6516f22 954 }
955
c1add777 956}
957
b001c73d 958function readings_equivalent( source, target ) {
959 var sourcetext = readingdata[source].text;
960 var targettext = readingdata[target].text;
961 if( sourcetext === targettext ) {
962 return true;
963 }
964 // Lowercase and strip punctuation from both and compare again
f069bb5c 965 var nonwc = XRegExp('[^\\p{L}\\s]|_');
966 var stlc = XRegExp.replace( sourcetext.toLocaleLowerCase(), nonwc, "", 'all' );
967 var ttlc = XRegExp.replace( targettext.toLocaleLowerCase(), nonwc, "", 'all' );
b001c73d 968 if( stlc === ttlc ) {
969 return true;
970 }
971 return false;
972}
973
9529f69c 974
b28e606e 975$(document).ready(function () {
9529f69c 976
977 timer = null;
b28e606e 978 relation_manager = new relation_factory();
979
c1add777 980 $('#update_workspace_button').data('locked', false);
b001c73d 981
982 // Set up the mouse events on the SVG enlargement
9529f69c 983 $('#enlargement').mousedown(function (event) {
b28e606e 984 $(this)
9529f69c 985 .data('down', true)
986 .data('x', event.clientX)
987 .data('y', event.clientY)
988 .data('scrollLeft', this.scrollLeft)
c1add777 989 stateTf = svg_root_element.getCTM().inverse();
990 var p = svg_root.createSVGPoint();
991 p.x = event.clientX;
992 p.y = event.clientY;
993 stateOrigin = p.matrixTransform(stateTf);
994
995 // Activate marquee if in interaction mode
996 if( $('#update_workspace_button').data('locked') == true ) { marquee.show( event ) };
997
998 event.returnValue = false;
999 event.preventDefault();
1000 return false;
b28e606e 1001 }).mouseup(function (event) {
a84ca4de 1002 marquee.select();
c1add777 1003 $(this).data('down', false);
b28e606e 1004 }).mousemove(function (event) {
9529f69c 1005 if( timer != null ) { clearTimeout(timer); }
1006 if ( ($(this).data('down') == true) && ($('#update_workspace_button').data('locked') == false) ) {
1007 var p = svg_root.createSVGPoint();
1008 p.x = event.clientX;
1009 p.y = event.clientY;
1010 p = p.matrixTransform(stateTf);
1011 var matrix = stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y);
1012 var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
76f05423 1013 svg_root_element.setAttribute("transform", s);
b28e606e 1014 }
c1add777 1015 marquee.expand( event );
76f05423 1016 event.returnValue = false;
1017 event.preventDefault();
b28e606e 1018 }).mousewheel(function (event, delta) {
9529f69c 1019 event.returnValue = false;
1020 event.preventDefault();
1021 if ( $('#update_workspace_button').data('locked') == false ) {
76f05423 1022 if (!delta || delta == null || delta == 0) delta = event.originalEvent.wheelDelta;
1023 if (!delta || delta == null || delta == 0) delta = -1 * event.originalEvent.detail;
9529f69c 1024 if( delta < -9 ) { delta = -9 };
1025 var z = 1 + delta/10;
76f05423 1026 z = delta > 0 ? 1 : -1;
1027 var g = svg_root_element;
1028 if (g && ((z<1 && (g.getScreenCTM().a * start_element_height) > 4.0) || (z>=1 && (g.getScreenCTM().a * start_element_height) < 100))) {
9529f69c 1029 var root = svg_root;
1030 var p = root.createSVGPoint();
76f05423 1031 p.x = event.originalEvent.clientX;
1032 p.y = event.originalEvent.clientY;
9529f69c 1033 p = p.matrixTransform(g.getCTM().inverse());
76f05423 1034 var scaleLevel = 1+(z/20);
1035 var k = root.createSVGMatrix().translate(p.x, p.y).scale(scaleLevel).translate(-p.x, -p.y);
9529f69c 1036 var matrix = g.getCTM().multiply(k);
1037 var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
1038 g.setAttribute("transform", s);
1039 }
1040 }
b28e606e 1041 }).css({
1042 'overflow' : 'hidden',
1043 'cursor' : '-moz-grab'
1044 });
1045
c1add777 1046
b001c73d 1047 // Set up the relationship creation dialog. This also functions as the reading
1048 // merge dialog where appropriate.
b001c73d 1049
30d0ba1e 1050 if( editable ) {
9231663d 1051 $( '#dialog-form' ).dialog( {
30d0ba1e 1052 autoOpen: false,
1053 height: 270,
51ca18ee 1054 width: 330,
30d0ba1e 1055 modal: true,
1056 buttons: {
9231663d 1057 'Merge readings': function( evt ) {
1058 $( evt.target ).button( 'disable' );
1059 $( '#status' ).empty();
1060 form_values = $( '#collapse_node_form' ).serialize();
b001c73d 1061 ncpath = getTextURL( 'merge' );
9231663d 1062 var jqjson = $.post( ncpath, form_values, function( data ) {
1063 merge_nodes( $( '#source_node_id' ).val(), $( '#target_node_id' ).val(), data );
bfb6d17f 1064 $(evt.target).button( 'enable' );
9231663d 1065 $( '#dialog-form' ).dialog( 'close' );
1066 } );
b001c73d 1067 },
1068 OK: function( evt ) {
9231663d 1069 $( evt.target ).button( 'disable' );
1070 $( '#status' ).empty();
1071 form_values = $( '#collapse_node_form' ).serialize();
30d0ba1e 1072 ncpath = getTextURL( 'relationships' );
9231663d 1073 var jqjson = $.post( ncpath, form_values, function( data ) {
1074 $.each( data, function( item, source_target ) {
30d0ba1e 1075 var source_found = get_ellipse( source_target[0] );
1076 var target_found = get_ellipse( source_target[1] );
9231663d 1077 var relation_found = $.inArray( source_target[2], $( '#keymap' ).data( 'relations' ) );
30d0ba1e 1078 if( source_found.size() && target_found.size() && relation_found > -1 ) {
1079 var relation = relation_manager.create( source_target[0], source_target[1], relation_found );
45ee3b96 1080 relation_manager.toggle_active( relation.attr('id') );
51ca18ee 1081 $.each( $('#collapse_node_form').serializeArray(), function( i, k ) {
1082 relation.data( k.name, k.value );
1083 });
7b54e481 1084 }
9231663d 1085 $(evt.target).button( 'enable' );
30d0ba1e 1086 });
9231663d 1087 $( '#dialog-form' ).dialog( 'close' );
30d0ba1e 1088 }, 'json' );
1089 },
1090 Cancel: function() {
9231663d 1091 $( this ).dialog( 'close' );
30d0ba1e 1092 }
1093 },
1094 create: function(event, ui) {
1095 $(this).data( 'relation_drawn', false );
a166dca8 1096 $('#rel_type').data( 'changed_after_open', false );
56e3972e 1097 $.each( relationship_types, function(index, typedef) {
1098 $('#rel_type').append( $('<option />').attr( "value", typedef.name ).text(typedef.name) );
1099 });
1100 $.each( relationship_scopes, function(index, value) {
1101 $('#scope').append( $('<option />').attr( "value", value ).text(value) );
a166dca8 1102 });
1103 // Handler to clear the annotation field, the first time the relationship is
1104 // changed after opening the form.
1105 $('#rel_type').change( function () {
1106 if( !$(this).data( 'changed_after_open' ) ) {
1107 $('#note').val('');
1108 }
1109 $(this).data( 'changed_after_open', true );
1110 });
30d0ba1e 1111 },
1112 open: function() {
b001c73d 1113 relation_manager.create_temporary(
1114 $('#source_node_id').val(), $('#target_node_id').val() );
1115 var buttonset = $(this).parent().find( '.ui-dialog-buttonset' )
1116 if( readings_equivalent( $('#source_node_id').val(),
1117 $('#target_node_id').val() ) ) {
1118 buttonset.find( "button:contains('Merge readings')" ).show();
1119 } else {
1120 buttonset.find( "button:contains('Merge readings')" ).hide();
1121 }
30d0ba1e 1122 $(".ui-widget-overlay").css("background", "none");
1123 $("#dialog_overlay").show();
1124 $("#dialog_overlay").height( $("#enlargement_container").height() );
1125 $("#dialog_overlay").width( $("#enlargement_container").innerWidth() );
1126 $("#dialog_overlay").offset( $("#enlargement_container").offset() );
a166dca8 1127 $('#rel_type').data( 'changed_after_open', false );
30d0ba1e 1128 },
1129 close: function() {
1130 relation_manager.remove_temporary();
1131 $( '#status' ).empty();
1132 $("#dialog_overlay").hide();
1133 }
1134 }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
21e6ebc7 1135 if( ajaxSettings.url == getTextURL('relationships')
1136 && ajaxSettings.type == 'POST' && jqXHR.status == 403 ) {
1137 var error;
1138 if( jqXHR.responseText.indexOf('do not have permission to modify') > -1 ) {
1139 error = 'You are not authorized to modify this tradition. (Try logging in again?)';
1140 } else {
1141 try {
1142 var errobj = jQuery.parseJSON( jqXHR.responseText );
1143 error = errobj.error + '</br>The relationship cannot be made.</p>';
1144 } catch(e) {
1145 error = jqXHR.responseText;
1146 }
1147 }
1148 $('#status').append( '<p class="error">Error: ' + error );
1149 }
1150 $(event.target).parent().find('.ui-button').button("enable");
30d0ba1e 1151 } );
1152 }
b28e606e 1153
b001c73d 1154 // Set up the relationship info display and deletion dialog.
9529f69c 1155 $( "#delete-form" ).dialog({
1156 autoOpen: false,
69a19c91 1157 height: 135,
d5b3c010 1158 width: 300,
9529f69c 1159 modal: false,
b001c73d 1160 buttons: {
1161 OK: function() { $( this ).dialog( "close" ); },
1162 "Delete all": function () { delete_relation( true ); },
1163 Delete: function() { delete_relation( false ); }
1164 },
9529f69c 1165 create: function(event, ui) {
30d0ba1e 1166 // TODO What is this logic doing?
a84ca4de 1167 // This scales the buttons in the dialog and makes it look proper
1168 // Not sure how essential it is, does anything break if it's not here?
9529f69c 1169 var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ).css( 'width', '100%' );
b001c73d 1170 buttonset.find( "button:contains('OK')" ).css( 'float', 'right' );
a84ca4de 1171 // A: This makes sure that the pop up delete relation dialogue for a hovered over
1172 // relation auto closes if the user doesn't engage (mouseover) with it.
9529f69c 1173 var dialog_aria = $("div[aria-labelledby='ui-dialog-title-delete-form']");
1174 dialog_aria.mouseenter( function() {
1175 if( mouseWait != null ) { clearTimeout(mouseWait) };
1176 })
1177 dialog_aria.mouseleave( function() {
1178 mouseWait = setTimeout( function() { $("#delete-form").dialog( "close" ) }, 2000 );
1179 })
1180 },
1181 open: function() {
b001c73d 1182 // Show the appropriate buttons...
1183 var buttonset = $(this).parent().find( '.ui-dialog-buttonset' )
1184 // If the user can't edit, show only the OK button
088a14af 1185 if( !editable ) {
b001c73d 1186 buttonset.find( "button:contains('Delete')" ).hide();
1187 // If the relationship scope is local, show only OK and Delete
088a14af 1188 } else if( $('#delete_relation_scope').text() === 'local' ) {
1189 $( this ).dialog( "option", "width", 160 );
b001c73d 1190 buttonset.find( "button:contains('Delete')" ).show();
1191 buttonset.find( "button:contains('Delete all')" ).hide();
1192 // Otherwise, show all three
088a14af 1193 } else {
1194 $( this ).dialog( "option", "width", 200 );
b001c73d 1195 buttonset.find( "button:contains('Delete')" ).show();
088a14af 1196 }
9529f69c 1197 mouseWait = setTimeout( function() { $("#delete-form").dialog( "close" ) }, 2000 );
1198 },
088a14af 1199 close: function() {}
9529f69c 1200 });
1201
a84ca4de 1202 $( "#multipleselect-form" ).dialog({
1203 autoOpen: false,
1204 height: 150,
1205 width: 250,
1206 modal: true,
5fe117bc 1207 buttons: {
22a70299 1208 Cancel: function() { $( this ).dialog( "close" ); },
1209 Detach: function ( evt ) {
1210 var self = $(this);
1211 $( evt.target ).button( "disable" );
1212 var form_values = $('#detach_collated_form').serialize();
1213 ncpath = getTextURL( 'duplicate' );
1214 var jqjson = $.post( ncpath, form_values, function(data) {
1215 detach_node( data );
1216 $(evt.target).button("enable");
1217 self.dialog( "close" );
1218 } );
1219 }
7ef4a584 1220 },
1221 create: function(event, ui) {
a84ca4de 1222 var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ).css( 'width', '100%' );
1223 buttonset.find( "button:contains('Cancel')" ).css( 'float', 'right' );
1224 },
1225 open: function() {
1226 $( this ).dialog( "option", "width", 200 );
e538eccb 1227 $(".ui-widget-overlay").css("background", "none");
5fe117bc 1228 $('#multipleselect-form-status').empty();
e538eccb 1229 $("#dialog_overlay").show();
1230 $("#dialog_overlay").height( $("#enlargement_container").height() );
1231 $("#dialog_overlay").width( $("#enlargement_container").innerWidth() );
1232 $("#dialog_overlay").offset( $("#enlargement_container").offset() );
a84ca4de 1233 },
e538eccb 1234 close: function() {
1235 marquee.unselect();
1236 $("#dialog_overlay").hide();
1237 }
5fe117bc 1238 }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
1239 if( ajaxSettings.url == getTextURL('duplicate')
1240 && ajaxSettings.type == 'POST' && jqXHR.status == 403 ) {
1241 var error;
1242 if( jqXHR.responseText.indexOf('do not have permission to modify') > -1 ) {
1243 error = 'You are not authorized to modify this tradition. (Try logging in again?)';
1244 } else {
1245 try {
1246 var errobj = jQuery.parseJSON( jqXHR.responseText );
1247 error = errobj.error + '</br>The relationship cannot be made.</p>';
1248 } catch(e) {
1249 error = jqXHR.responseText;
1250 }
1251 }
1252 $('#multipleselect-form-status').append( '<p class="error">Error: ' + error );
1253 }
1254 $(event.target).parent().find('.ui-button').button("enable");
1255 });
1256
a84ca4de 1257
088a14af 1258 // Helpers for relationship deletion
1259
1260 function delete_relation( scopewide ) {
1261 form_values = $('#delete_relation_form').serialize();
1262 if( scopewide ) {
1263 form_values += "&scopewide=true";
1264 }
1265 ncpath = getTextURL( 'relationships' );
1266 var jqjson = $.ajax({ url: ncpath, data: form_values, success: function(data) {
1267 $.each( data, function(item, source_target) {
1268 relation_manager.remove( get_relation_id( source_target[0], source_target[1] ) );
1269 });
1270 $( "#delete-form" ).dialog( "close" );
1271 }, dataType: 'json', type: 'DELETE' });
1272 }
1273
1274 function toggle_relation_active( node_id ) {
1275 $('#svgenlargement .relation').find( "title:contains('" + node_id + "')" ).each( function(index) {
1276 matchid = new RegExp( "^" + node_id );
1277 if( $(this).text().match( matchid ) != null ) {
1278 var relation_id = $(this).parent().attr('id');
1279 relation_manager.toggle_active( relation_id );
1280 };
1281 });
1282 }
1283
487674b9 1284 // function for reading form dialog should go here;
1285 // just hide the element for now if we don't have morphology
1286 if( can_morphologize ) {
1287 $('#reading-form').dialog({
1288 autoOpen: false,
1289 // height: 400,
1290 width: 450,
1291 modal: true,
1292 buttons: {
1293 Cancel: function() {
1294 $( this ).dialog( "close" );
1295 },
1296 Update: function( evt ) {
1297 // Disable the button
1298 $(evt.target).button("disable");
1299 $('#reading_status').empty();
1300 var reading_id = $('#reading_id').val()
1301 form_values = {
1302 'id' : reading_id,
1303 'is_nonsense': $('#reading_is_nonsense').is(':checked'),
1304 'grammar_invalid': $('#reading_grammar_invalid').is(':checked'),
1305 'normal_form': $('#reading_normal_form').val() };
1306 // Add the morphology values
1307 $('.reading_morphology').each( function() {
1308 if( $(this).val() != '(Click to select)' ) {
1309 var rmid = $(this).attr('id');
1310 rmid = rmid.substring(8);
1311 form_values[rmid] = $(this).val();
1312 }
45ee3b96 1313 });
487674b9 1314 // Make the JSON call
1315 ncpath = getReadingURL( reading_id );
1316 var reading_element = readingdata[reading_id];
1317 // $(':button :contains("Update")').attr("disabled", true);
1318 var jqjson = $.post( ncpath, form_values, function(data) {
1319 $.each( data, function(key, value) {
1320 reading_element[key] = value;
1321 });
1322 if( $('#update_workspace_button').data('locked') == false ) {
1323 color_inactive( get_ellipse( reading_id ) );
1324 }
1325 $(evt.target).button("enable");
1326 $( "#reading-form" ).dialog( "close" );
1327 });
1328 // Re-color the node if necessary
1329 return false;
1330 }
1331 },
1332 create: function() {
30d0ba1e 1333 if( !editable ) {
1334 // Get rid of the disallowed editing UI bits
1335 $( this ).dialog( "option", "buttons",
1336 [{ text: "OK", click: function() { $( this ).dialog( "close" ); }}] );
1337 $('#reading_relemmatize').hide();
1338 }
487674b9 1339 },
1340 open: function() {
1341 $(".ui-widget-overlay").css("background", "none");
1342 $("#dialog_overlay").show();
1343 $('#reading_status').empty();
1344 $("#dialog_overlay").height( $("#enlargement_container").height() );
1345 $("#dialog_overlay").width( $("#enlargement_container").innerWidth() );
1346 $("#dialog_overlay").offset( $("#enlargement_container").offset() );
1347 $("#reading-form").parent().find('.ui-button').button("enable");
1348 },
1349 close: function() {
1350 $("#dialog_overlay").hide();
1351 }
1352 }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
21e6ebc7 1353 if( ajaxSettings.url.lastIndexOf( getReadingURL('') ) > -1
487674b9 1354 && ajaxSettings.type == 'POST' && jqXHR.status == 403 ) {
21e6ebc7 1355 var error;
1356 if( jqXHR.responseText.indexOf('do not have permission to modify') > -1 ) {
1357 error = 'You are not authorized to modify this tradition. (Try logging in again?)';
1358 } else {
1359 try {
1360 var errobj = jQuery.parseJSON( jqXHR.responseText );
1361 error = errobj.error + '</br>The relationship cannot be made.</p>';
1362 } catch(e) {
1363 error = jqXHR.responseText;
1364 }
1365 }
1366 $('#status').append( '<p class="error">Error: ' + error );
1367 }
1368 $(event.target).parent().find('.ui-button').button("enable");
487674b9 1369 });
1370 } else {
1371 $('#reading-form').hide();
45ee3b96 1372 }
4c41c02c 1373
76f05423 1374
b28e606e 1375 $('#update_workspace_button').click( function() {
30d0ba1e 1376 if( !editable ) {
1377 return;
1378 }
b28e606e 1379 var svg_enlargement = $('#svgenlargement').svg().svg('get').root();
76f05423 1380 mouse_scale = svg_root_element.getScreenCTM().a;
9529f69c 1381 if( $(this).data('locked') == true ) {
1382 $('#svgenlargement ellipse' ).each( function( index ) {
1383 if( $(this).data( 'node_obj' ) != null ) {
1384 $(this).data( 'node_obj' ).ungreyout_edges();
e538eccb 1385 $(this).data( 'node_obj' ).set_selectable( false );
1386 color_inactive( $(this) );
9529f69c 1387 var node_id = $(this).data( 'node_obj' ).get_id();
1388 toggle_relation_active( node_id );
1389 $(this).data( 'node_obj', null );
1390 }
b28e606e 1391 })
b28e606e 1392 $(this).data('locked', false);
9529f69c 1393 $(this).css('background-position', '0px 44px');
b28e606e 1394 } else {
9529f69c 1395 var left = $('#enlargement').offset().left;
1396 var right = left + $('#enlargement').width();
76f05423 1397 var tf = svg_root_element.getScreenCTM().inverse();
9529f69c 1398 var p = svg_root.createSVGPoint();
1399 p.x=left;
1400 p.y=100;
1401 var cx_min = p.matrixTransform(tf).x;
1402 p.x=right;
1403 var cx_max = p.matrixTransform(tf).x;
1404 $('#svgenlargement ellipse').each( function( index ) {
1405 var cx = parseInt( $(this).attr('cx') );
1406 if( cx > cx_min && cx < cx_max) {
1407 if( $(this).data( 'node_obj' ) == null ) {
1408 $(this).data( 'node_obj', new node_obj( $(this) ) );
1409 } else {
e538eccb 1410 $(this).data( 'node_obj' ).set_selectable( true );
9529f69c 1411 }
1412 $(this).data( 'node_obj' ).greyout_edges();
1413 var node_id = $(this).data( 'node_obj' ).get_id();
1414 toggle_relation_active( node_id );
b28e606e 1415 }
9529f69c 1416 });
1417 $(this).css('background-position', '0px 0px');
b28e606e 1418 $(this).data('locked', true );
b28e606e 1419 }
1420 });
30d0ba1e 1421
1422 if( !editable ) {
1423 // Hide the unused elements
1424 $('#dialog-form').hide();
1425 $('#update_workspace_button').hide();
1426 }
1427
b28e606e 1428
e847b186 1429 $('.helptag').popupWindow({
1430 height:500,
1431 width:800,
1432 top:50,
1433 left:50,
1434 scrollbars:1
1435 });
1436
76f05423 1437 expandFillPageClients();
1438 $(window).resize(function() {
1439 expandFillPageClients();
1440 });
1441
9529f69c 1442});
b28e606e 1443
1444
76f05423 1445function expandFillPageClients() {
1446 $('.fillPage').each(function () {
1447 $(this).height($(window).height() - $(this).offset().top - MARGIN);
1448 });
1449}
1450
1451function loadSVG(svgData) {
1452 var svgElement = $('#svgenlargement');
1453
1454 $(svgElement).svg('destroy');
1455
1456 $(svgElement).svg({
1457 loadURL: svgData,
1458 onLoad : svgEnlargementLoaded
1459 });
1460}
1461
1462
c1add777 1463
76f05423 1464/* OS Gadget stuff
1465
1466function svg_select_callback(topic, data, subscriberData) {
1467 svgData = data;
1468 loadSVG(svgData);
1469}
1470
1471function loaded() {
1472 var prefs = new gadgets.Prefs();
1473 var preferredHeight = parseInt(prefs.getString('height'));
1474 if (gadgets.util.hasFeature('dynamic-height')) gadgets.window.adjustHeight(preferredHeight);
1475 expandFillPageClients();
1476}
1477
1478if (gadgets.util.hasFeature('pubsub-2')) {
1479 gadgets.HubSettings.onConnect = function(hum, suc, err) {
1480 subId = gadgets.Hub.subscribe("interedition.svg.selected", svg_select_callback);
1481 loaded();
1482 };
1483}
1484else gadgets.util.registerOnLoadHandler(loaded);
1485*/