source: other-projects/nz-flag-design/trunk/design-2d/Original editor.method.ac/editor/extensions/ext-markers.js@ 29468

Last change on this file since 29468 was 29468, checked in by sjs49, 9 years ago

Initial commit for editor.method.ac for flag design

  • Property svn:executable set to *
File size: 16.0 KB
Line 
1/*
2 * ext-markers.js
3 *
4 * Licensed under the Apache License, Version 2
5 *
6 * Copyright(c) 2010 Will Schleter
7 * based on ext-arrows.js by Copyright(c) 2010 Alexis Deveria
8 *
9 * This extension provides for the addition of markers to the either end
10 * or the middle of a line, polyline, path, polygon.
11 *
12 * Markers may be either a graphic or arbitary text
13 *
14 * to simplify the coding and make the implementation as robust as possible,
15 * markers are not shared - every object has its own set of markers.
16 * this relationship is maintained by a naming convention between the
17 * ids of the markers and the ids of the object
18 *
19 * The following restrictions exist for simplicty of use and programming
20 * objects and their markers to have the same color
21 * marker size is fixed
22 * text marker font, size, and attributes are fixed
23 * an application specific attribute - se_type - is added to each marker element
24 * to store the type of marker
25 *
26 * TODO:
27 * remove some of the restrictions above
28 * add option for keeping text aligned to horizontal
29 * add support for dimension extension lines
30 *
31 */
32
33methodDraw.addExtension("Markers", function(S) {
34 var svgcontent = S.svgcontent,
35 addElem = S.addSvgElementFromJson,
36 selElems;
37
38 var mtypes = ['start','mid','end'];
39
40 var marker_prefix = 'se_marker_';
41 var id_prefix = 'mkr_';
42
43 // note - to add additional marker types add them below with a unique id
44 // and add the associated icon(s) to marker-icons.svg
45 // the geometry is normallized to a 100x100 box with the origin at lower left
46 // Safari did not like negative values for low left of viewBox
47 // remember that the coordinate system has +y downward
48 var marker_types = {
49 nomarker: {},
50 leftarrow:
51 {element:'path', attr:{d:'M0,50 L100,90 L70,50 L100,10 Z'}},
52 rightarrow:
53 {element:'path', attr:{d:'M100,50 L0,90 L30,50 L0,10 Z'}},
54 textmarker:
55 {element:'text', attr: {x:0, y:0,'stroke-width':0,'stroke':'none','font-size':75,'font-family':'serif','text-anchor':'left',
56 'xml:space': 'preserve'}},
57 forwardslash:
58 {element:'path', attr:{d:'M30,100 L70,0'}},
59 reverseslash:
60 {element:'path', attr:{d:'M30,0 L70,100'}},
61 verticalslash:
62 {element:'path', attr:{d:'M50,0 L50,100'}},
63 box:
64 {element:'path', attr:{d:'M20,20 L20,80 L80,80 L80,20 Z'}},
65 star:
66 {element:'path', attr:{d:'M10,30 L90,30 L20,90 L50,10 L80,90 Z'}},
67 xmark:
68 {element:'path', attr:{d:'M20,80 L80,20 M80,80 L20,20'}},
69 triangle:
70 {element:'path', attr:{d:'M10,80 L50,20 L80,80 Z'}},
71 mcircle:
72 {element:'circle', attr:{r:30, cx:50, cy:50}}
73 }
74
75
76 var lang_list = {
77 "en":[
78 {id: "start_marker_list", title: "Select start marker type" },
79 {id: "mid_marker_list", title: "Select mid marker type" },
80 {id: "end_marker_list", title: "Select end marker type" },
81 {id: "nomarker", title: "No Marker" },
82 {id: "leftarrow", title: "Left Arrow" },
83 {id: "rightarrow", title: "Right Arrow" },
84 {id: "textmarker", title: "Text Marker" },
85 {id: "forwardslash", title: "Forward Slash" },
86 {id: "reverseslash", title: "Reverse Slash" },
87 {id: "verticalslash", title: "Vertical Slash" },
88 {id: "box", title: "Box" },
89 {id: "star", title: "Star" },
90 {id: "xmark", title: "X" },
91 {id: "triangle", title: "Triangle" },
92 {id: "mcircle", title: "Circle" },
93 {id: "leftarrow_o", title: "Open Left Arrow" },
94 {id: "rightarrow_o", title: "Open Right Arrow" },
95 {id: "box_o", title: "Open Box" },
96 {id: "star_o", title: "Open Star" },
97 {id: "triangle_o", title: "Open Triangle" },
98 {id: "mcircle_o", title: "Open Circle" }
99 ]
100 };
101
102
103 // duplicate shapes to support unfilled (open) marker types with an _o suffix
104 $.each(['leftarrow','rightarrow','box','star','mcircle','triangle'],function(i,v) {
105 marker_types[v+'_o'] = marker_types[v];
106 });
107
108 // elem = a graphic element will have an attribute like marker-start
109 // attr - marker-start, marker-mid, or marker-end
110 // returns the marker element that is linked to the graphic element
111 function getLinked(elem, attr) {
112 var str = elem.getAttribute(attr);
113 if(!str) return null;
114 var m = str.match(/\(\#(.*)\)/);
115 if(!m || m.length !== 2) {
116 return null;
117 }
118 return S.getElem(m[1]);
119 }
120
121 //toggles context tool panel off/on
122 //sets the controls with the selected element's settings
123 function showPanel(on) {
124 $('#marker_panel').toggle(on);
125 if ($('#marker_panel_title').length < 1) {
126 $('#marker_panel').prepend("<h4 id='marker_panel_title'>Arrows</h4>")
127 }
128
129 if(on) {
130 var el = selElems[0];
131 var val;
132 var ci;
133
134 $.each(mtypes, function(i, pos) {
135 var m=getLinked(el,"marker-"+pos);
136 var txtbox = $('#'+pos+'_marker');
137 if (!m) {
138 val='\\nomarker';
139 ci=val;
140 txtbox.hide() // hide text box
141 } else {
142 if (!m.attributes.se_type) return; // not created by this extension
143 val='\\'+m.attributes.se_type.textContent;
144 ci=val;
145 if (val=='\\textmarker') {
146 val=m.lastChild.textContent;
147 //txtbox.show(); // show text box
148 } else {
149 txtbox.hide() // hide text box
150 }
151 }
152 txtbox.val(val);
153 setIcon(pos,ci);
154 })
155 }
156 }
157
158 function addMarker(id, val) {
159 var txt_box_bg = '#ffffff';
160 var txt_box_border = 'none';
161 var txt_box_stroke_width = 0;
162
163 var marker = S.getElem(id);
164
165 if (marker) return;
166
167 if (val=='' || val=='\\nomarker') return;
168
169 var el = selElems[0];
170 var color = el.getAttribute('stroke');
171 //NOTE: Safari didn't like a negative value in viewBox
172 //so we use a standardized 0 0 100 100
173 //with 50 50 being mapped to the marker position
174 var refX = 50;
175 var refY = 50;
176 var viewBox = "0 0 100 100";
177 var markerWidth = 5;
178 var markerHeight = 5;
179 var strokeWidth = 10;
180 if (val.substr(0,1)=='\\') se_type=val.substr(1);
181 else se_type='textmarker';
182
183 if (!marker_types[se_type]) return; // an unknown type!
184
185 // create a generic marker
186 marker = addElem({
187 "element": "marker",
188 "attr": {
189 "id": id,
190 "markerUnits": "strokeWidth",
191 "orient": "auto",
192 "style": "pointer-events:none",
193 "se_type": se_type
194 }
195 });
196
197 if (se_type!='textmarker') {
198 var mel = addElem(marker_types[se_type]);
199 var fillcolor = color;
200 if (se_type.substr(-2)=='_o') fillcolor='none';
201 mel.setAttribute('fill',fillcolor);
202 mel.setAttribute('stroke',color);
203 mel.setAttribute('stroke-width',strokeWidth);
204 marker.appendChild(mel);
205 } else {
206 var text = addElem(marker_types[se_type]);
207 // have to add text to get bounding box
208 text.textContent = val;
209 var tb=text.getBBox();
210 //alert( tb.x + " " + tb.y + " " + tb.width + " " + tb.height);
211 var pad=1;
212 var bb = tb;
213 bb.x = 0;
214 bb.y = 0;
215 bb.width += pad*2;
216 bb.height += pad*2;
217 // shift text according to its size
218 text.setAttribute('x', pad);
219 text.setAttribute('y', bb.height - pad - tb.height/4); // kludge?
220 text.setAttribute('fill',color);
221 refX = bb.width/2+pad;
222 refY = bb.height/2+pad;
223 viewBox = bb.x + " " + bb.y + " " + bb.width + " " + bb.height;
224 markerWidth =bb.width/10;
225 markerHeight = bb.height/10;
226
227 var box = addElem({
228 "element": "rect",
229 "attr": {
230 "x": bb.x,
231 "y": bb.y,
232 "width": bb.width,
233 "height": bb.height,
234 "fill": txt_box_bg,
235 "stroke": txt_box_border,
236 "stroke-width": txt_box_stroke_width
237 }
238 });
239 marker.setAttribute("orient",0);
240 marker.appendChild(box);
241 marker.appendChild(text);
242 }
243
244 marker.setAttribute("viewBox",viewBox);
245 marker.setAttribute("markerWidth", markerWidth);
246 marker.setAttribute("markerHeight", markerHeight);
247 marker.setAttribute("refX", refX);
248 marker.setAttribute("refY", refY);
249 S.findDefs().appendChild(marker);
250
251 return marker;
252 }
253
254
255 function setMarker() {
256 var poslist={'start_marker':'start','mid_marker':'mid','end_marker':'end'};
257 var pos = poslist[this.id];
258 var marker_name = 'marker-'+pos;
259 var val = this.value;
260 var el = selElems[0];
261 var marker = getLinked(el, marker_name);
262 if (marker) $(marker).remove();
263 el.removeAttribute(marker_name);
264 if (val=='') val='\\nomarker';
265 if (val=='\\nomarker') {
266 setIcon(pos,val);
267 S.call("changed", selElems);
268 return;
269 }
270 // Set marker on element
271 var id = marker_prefix + pos + '_' + el.id;
272 addMarker(id, val);
273 svgCanvas.changeSelectedAttribute(marker_name, "url(#" + id + ")");
274 if (el.tagName == "line" && pos=='mid') el=convertline(el);
275 S.call("changed", selElems);
276 setIcon(pos,val);
277 }
278
279 function convertline(elem) {
280 // this routine came from the connectors extension
281 // it is needed because midpoint markers don't work with line elements
282 if (!(elem.tagName == "line")) return elem;
283
284 // Convert to polyline to accept mid-arrow
285
286 var x1 = elem.getAttribute('x1')-0;
287 var x2 = elem.getAttribute('x2')-0;
288 var y1 = elem.getAttribute('y1')-0;
289 var y2 = elem.getAttribute('y2')-0;
290 var id = elem.id;
291
292 var mid_pt = (' '+((x1+x2)/2)+','+((y1+y2)/2) + ' ');
293 var pline = addElem({
294 "element": "polyline",
295 "attr": {
296 "points": (x1+','+y1+ mid_pt +x2+','+y2),
297 "stroke": elem.getAttribute('stroke'),
298 "stroke-width": elem.getAttribute('stroke-width'),
299 "fill": "none",
300 "opacity": elem.getAttribute('opacity') || 1
301 }
302 });
303 $.each(mtypes, function(i, pos) { // get any existing marker definitions
304 var nam = 'marker-'+pos;
305 var m = elem.getAttribute(nam);
306 if (m) pline.setAttribute(nam,elem.getAttribute(nam));
307 });
308
309 var batchCmd = new S.BatchCommand();
310 batchCmd.addSubCommand(new S.RemoveElementCommand(elem, elem.parentNode));
311 batchCmd.addSubCommand(new S.InsertElementCommand(pline));
312
313 $(elem).after(pline).remove();
314 svgCanvas.clearSelection();
315 pline.id = id;
316 svgCanvas.addToSelection([pline]);
317 S.addCommandToHistory(batchCmd);
318 return pline;
319 }
320
321 // called when the main system modifies an object
322 // this routine changes the associated markers to be the same color
323 function colorChanged(elem) {
324 var color = elem.getAttribute('stroke');
325
326 $.each(mtypes, function(i, pos) {
327 var marker = getLinked(elem, 'marker-'+pos);
328 if (!marker) return;
329 if (!marker.attributes.se_type) return; //not created by this extension
330 var ch = marker.lastElementChild;
331 if (!ch) return;
332 var curfill = ch.getAttribute("fill");
333 var curstroke = ch.getAttribute("stroke")
334 if (curfill && curfill!='none') ch.setAttribute("fill",color);
335 if (curstroke && curstroke!='none') ch.setAttribute("stroke",color);
336 });
337 }
338
339 // called when the main system creates or modifies an object
340 // primary purpose is create new markers for cloned objects
341 function updateReferences(el) {
342 $.each(mtypes, function (i,pos) {
343 var id = marker_prefix + pos + '_' + el.id;
344 var marker_name = 'marker-'+pos;
345 var marker = getLinked(el, marker_name);
346 if (!marker || !marker.attributes.se_type) return; //not created by this extension
347 var url = el.getAttribute(marker_name);
348 if (url) {
349 var len = el.id.length;
350 var linkid = url.substr(-len-1,len);
351 if (el.id != linkid) {
352 var val = $('#'+pos+'_marker').attr('value');
353 addMarker(id, val);
354 svgCanvas.changeSelectedAttribute(marker_name, "url(#" + id + ")");
355 if (el.tagName == "line" && pos=='mid') el=convertline(el);
356 S.call("changed", selElems);
357 }
358 }
359 });
360 }
361
362 // simulate a change event a text box that stores the current element's marker type
363 function triggerTextEntry(pos,val) {
364 $('#'+pos+'_marker').val(val);
365 $('#'+pos+'_marker').change();
366 var txtbox = $('#'+pos+'_marker');
367 //if (val.substr(0,1)=='\\') txtbox.hide();
368 //else txtbox.show();
369 }
370
371 function setIcon(pos,id) {
372 if (id.substr(0,1)!='\\') id='\\textmarker'
373 var ci = '#'+id_prefix+pos+'_'+id.substr(1);
374 methodDraw.setIcon('#cur_' + pos +'_marker_list', $(ci).children());
375 $(ci).addClass('current').siblings().removeClass('current');
376 }
377
378 function setMarkerSet(obj) {
379 var parts = this.id.split('_');
380 var set = parts[2];
381 switch (set) {
382 case 'off':
383 triggerTextEntry('start','\\nomarker');
384 triggerTextEntry('mid','\\nomarker');
385 triggerTextEntry('end','\\nomarker');
386 break;
387 case 'dimension':
388 triggerTextEntry('start','\\leftarrow');
389 triggerTextEntry('end','\\rightarrow');
390 showTextPrompt('mid');
391 break;
392 case 'label':
393 triggerTextEntry('mid','\\nomarker');
394 triggerTextEntry('end','\\rightarrow');
395 showTextPrompt('start');
396 break;
397 }
398 }
399
400 function showTextPrompt(pos) {
401 var def = $('#'+pos+'_marker').val();
402 if (def.substr(0,1)=='\\') def='';
403 $.prompt('Enter text for ' + pos + ' marker', def , function(txt) { if (txt) triggerTextEntry(pos,txt); });
404 }
405
406 // callback function for a toolbar button click
407 function setArrowFromButton(obj) {
408
409 var parts = this.id.split('_');
410 var pos = parts[1];
411 var val = parts[2];
412 if (parts[3]) val+='_'+parts[3];
413
414 if (val!='textmarker') {
415 triggerTextEntry(pos,'\\'+val);
416 } else {
417 showTextPrompt(pos);
418 }
419 }
420
421 function getTitle(lang,id) {
422 var list = lang_list[lang];
423 for (var i in list) {
424 if (list[i].id==id) return list[i].title;
425 }
426 return id;
427 }
428
429
430 // build the toolbar button array from the marker definitions
431 // TODO: need to incorporate language specific titles
432 function buildButtonList() {
433 var buttons=[];
434 var i=0;
435/*
436 buttons.push({
437 id:id_prefix + 'markers_off',
438 title:'Turn off all markers',
439 type:'context',
440 events: { 'click': setMarkerSet },
441 panel: 'marker_panel'
442 });
443 buttons.push({
444 id:id_prefix + 'markers_dimension',
445 title:'Dimension',
446 type:'context',
447 events: { 'click': setMarkerSet },
448 panel: 'marker_panel'
449 });
450 buttons.push({
451 id:id_prefix + 'markers_label',
452 title:'Label',
453 type:'context',
454 events: { 'click': setMarkerSet },
455 panel: 'marker_panel'
456 });
457*/
458 $.each(mtypes,function(k,pos) {
459 var listname = pos + "_marker_list";
460 var def = true;
461 $.each(marker_types,function(id,v) {
462 var title = getTitle('en',id);
463 buttons.push({
464 id:id_prefix + pos + "_" + id,
465 svgicon:id,
466 title:title,
467 type:'context',
468 events: { 'click': setArrowFromButton },
469 panel:'marker_panel',
470 list: listname,
471 isDefault: def
472 });
473 def = false;
474 });
475 });
476 return buttons;
477 }
478
479 return {
480 name: "Markers",
481 svgicons: "extensions/markers-icons.xml",
482 buttons: buildButtonList(),
483 context_tools: [
484 {
485 type: "input",
486 panel: "marker_panel",
487 title: "Start marker",
488 id: "start_marker",
489 label: "Start",
490 size: 3,
491 events: { change: setMarker }
492 },{
493 type: "button-select",
494 panel: "marker_panel",
495 title: getTitle('en','start_marker_list'),
496 id: "start_marker_list",
497 colnum: 3,
498 events: { change: setArrowFromButton }
499 },{
500 type: "input",
501 panel: "marker_panel",
502 title: "Middle marker",
503 id: "mid_marker",
504 label: "Middle",
505 defval: "",
506 size: 3,
507 events: { change: setMarker }
508 },{
509 type: "button-select",
510 panel: "marker_panel",
511 title: getTitle('en','mid_marker_list'),
512 id: "mid_marker_list",
513 colnum: 3,
514 events: { change: setArrowFromButton }
515 },{
516 type: "input",
517 panel: "marker_panel",
518 title: "End marker",
519 id: "end_marker",
520 label: "End",
521 size: 3,
522 events: { change: setMarker }
523 },{
524 type: "button-select",
525 panel: "marker_panel",
526 title: getTitle('en','end_marker_list'),
527 id: "end_marker_list",
528 colnum: 3,
529 events: { change: setArrowFromButton }
530 } ],
531 callback: function() {
532 $('#marker_panel').addClass('toolset').hide();
533
534 },
535 addLangData: function(lang) {
536 return { data: lang_list[lang] };
537 },
538
539 selectedChanged: function(opts) {
540 // Use this to update the current selected elements
541 //console.log('selectChanged',opts);
542 selElems = opts.elems;
543
544 var i = selElems.length;
545 var marker_elems = ['line','path','polyline','polygon'];
546
547 while(i--) {
548 var elem = selElems[i];
549 if(elem && $.inArray(elem.tagName, marker_elems) != -1) {
550 if(opts.selectedElement && !opts.multiselected) {
551 showPanel(true);
552 } else {
553 showPanel(false);
554 }
555 } else {
556 showPanel(false);
557 }
558 }
559 },
560
561 elementChanged: function(opts) {
562 //console.log('elementChanged',opts);
563 var elem = opts.elems[0];
564 if(elem && (
565 elem.getAttribute("marker-start") ||
566 elem.getAttribute("marker-mid") ||
567 elem.getAttribute("marker-end")
568 )) {
569 colorChanged(elem);
570 updateReferences(elem);
571 }
572 changing_flag = false;
573 }
574 };
575});
576
Note: See TracBrowser for help on using the repository browser.