/*globals globalStorage, widget, svgEditor, svgedit, canvg, DOMParser, FileReader, jQuery, $ */ /*jslint vars: true, eqeq: true, todo: true, forin: true, continue: true, regexp: true */ /* * svg-editor.js * * Licensed under the MIT License * * Copyright(c) 2010 Alexis Deveria * Copyright(c) 2010 Pavol Rusnak * Copyright(c) 2010 Jeff Schiller * Copyright(c) 2010 Narendra Sisodiya * */ // Dependencies: // 1) units.js // 2) browser.js // 3) svgcanvas.js (function() {'use strict'; if (window.svgEditor) { return; } window.svgEditor = (function($) { var svgCanvas, Editor = {}, isReady = false, defaultPrefs = { lang: 'en', iconsize: 'm', bkgd_color: '#FFF', bkgd_url: '', img_save: 'embed' }, curPrefs = {}, // Note: Difference between Prefs and Config is that Prefs can be // changed in the UI and are stored in the browser, config can not curConfig = { canvasName: 'default', canvas_expansion: 3, dimensions: [640,480], initFill: { color: 'FF0000', // solid red opacity: 1 }, initStroke: { width: 5, color: '000000', // solid black opacity: 1 }, initOpacity: 1, imgPath: 'images/', langPath: 'locale/', extPath: 'extensions/', jGraduatePath: 'jgraduate/images/', extensions: [ 'ext-markers.js', 'ext-connector.js', 'ext-eyedropper.js', 'ext-shapes.js', 'ext-imagelib.js', 'ext-grid.js', 'ext-polygon.js', 'ext-star.js', 'ext-panning.js' ], initTool: 'select', wireframe: false, colorPickerCSS: null, gridSnapping: false, gridColor: '#000', baseUnit: 'px', snappingStep: 10, showRulers: true }, uiStrings = Editor.uiStrings = { common: { ok: 'OK', cancel: 'Cancel', key_up: 'Up', key_down: 'Down', key_backspace: 'Backspace', key_del: 'Del' }, // This is needed if the locale is English, since the locale strings are not read in that instance. layers: { layer: 'Layer' }, notification: { invalidAttrValGiven: 'Invalid value given', noContentToFitTo: 'No content to fit to', dupeLayerName: 'There is already a layer named that!', enterUniqueLayerName: 'Please enter a unique layer name', enterNewLayerName: 'Please enter the new layer name', layerHasThatName: 'Layer already has that name', QmoveElemsToLayer: 'Move selected elements to layer \'%s\'?', QwantToClear: 'Do you want to clear the drawing?\nThis will also erase your undo history!', QwantToOpen: 'Do you want to open a new file?\nThis will also erase your undo history!', QerrorsRevertToSource: 'There were parsing errors in your SVG source.\nRevert back to original SVG source?', QignoreSourceChanges: 'Ignore changes made to SVG source?', featNotSupported: 'Feature not supported', enterNewImgURL: 'Enter the new image URL', defsFailOnSave: 'NOTE: Due to a bug in your browser, this image may appear wrong (missing gradients or elements). It will however appear correct once actually saved.', loadingImage: 'Loading image, please wait...', saveFromBrowser: 'Select \'Save As...\' in your browser to save this image as a %s file.', noteTheseIssues: 'Also note the following issues: ', unsavedChanges: 'There are unsaved changes.', enterNewLinkURL: 'Enter the new hyperlink URL', errorLoadingSVG: 'Error: Unable to load SVG data', URLloadFail: 'Unable to load from URL', retrieving: 'Retrieving \'%s\' ...' } }, customHandlers = {}; function loadSvgString(str, callback) { var success = svgCanvas.setSvgString(str) !== false; callback = callback || $.noop; if (success) { callback(true); } else { $.alert(uiStrings.notification.errorLoadingSVG, function() { callback(false); }); } } Editor.curConfig = curConfig; Editor.tool_scale = 1; // Store and retrieve preferences $.pref = function(key, val) { if (val) { curPrefs[key] = val; } key = 'svg-edit-' + key; var d, result, host = location.hostname, onWeb = host && host.indexOf('.') >= 0, store = (val != undefined), storage = false; // Some FF versions throw security errors here try { if (window.localStorage) { // && onWeb removed so Webkit works locally storage = localStorage; } } catch(e) {} try { if (window.globalStorage && onWeb) { storage = globalStorage[host]; } } catch(e2) {} if (storage) { if (store) { storage.setItem(key, val); } else if (storage.getItem(key)) { return String(storage.getItem(key)); // Convert to string for FF (.value fails in Webkit) } } else if (window.widget) { if (store) { widget.setPreferenceForKey(val, key); } else { return widget.preferenceForKey(key); } } else { if (store) { d = new Date(); d.setTime(d.getTime() + 31536000000); val = encodeURIComponent(val); document.cookie = key+'='+val+'; expires='+d.toUTCString(); } else { result = document.cookie.match(new RegExp(key + '=([^;]+)')); return result ? decodeURIComponent(result[1]) : ''; } } }; Editor.setConfig = function(opts) { $.each(opts, function(key, val) { // Only allow prefs defined in defaultPrefs if (defaultPrefs[key]) { $.pref(key, val); } }); $.extend(true, curConfig, opts); if (opts.extensions) { curConfig.extensions = opts.extensions; } }; // Extension mechanisms may call setCustomHandlers with three functions: opts.open, opts.save, and opts.exportImage // opts.open's responsibilities are: // - invoke a file chooser dialog in 'open' mode // - let user pick a SVG file // - calls setCanvas.setSvgString() with the string contents of that file // opts.save's responsibilities are: // - accept the string contents of the current document // - invoke a file chooser dialog in 'save' mode // - save the file to location chosen by the user // opts.exportImage's responsibilities (with regard to the object it is supplied in its 2nd argument) are: // - inform user of any issues supplied via the "issues" property // - convert the "svg" property SVG string into an image for export; // utilize the properties "type" (currently 'PNG', 'JPEG', 'BMP', // 'WEBP'), "mimeType", and "quality" (for 'JPEG' and 'WEBP' // types) to determine the proper output. Editor.setCustomHandlers = function(opts) { Editor.ready(function() { if (opts.open) { $('#tool_open > input[type="file"]').remove(); $('#tool_open').show(); svgCanvas.open = opts.open; } if (opts.save) { Editor.showSaveWarning = false; svgCanvas.bind('saved', opts.save); } if (opts.exportImage || opts.pngsave) { // Deprecating pngsave svgCanvas.bind('exported', opts.exportImage || opts.pngsave); } customHandlers = opts; }); }; Editor.randomizeIds = function() { svgCanvas.randomizeIds(arguments); }; Editor.init = function() { // For external openers (function() { // let the opener know SVG Edit is ready var svgEditorReadyEvent, w = window.opener; if (w) { try { svgEditorReadyEvent = w.document.createEvent('Event'); svgEditorReadyEvent.initEvent('svgEditorReady', true, true); w.document.documentElement.dispatchEvent(svgEditorReadyEvent); } catch(e) {} } }()); (function() { // Load config/data from URL if given var src, qstr, urldata = $.deparam.querystring(true); if (!$.isEmptyObject(urldata)) { if (urldata.dimensions) { urldata.dimensions = urldata.dimensions.split(','); } if (urldata.bkgd_color) { urldata.bkgd_color = '#' + urldata.bkgd_color; } if (urldata.extensions) { // For security reasons, disallow cross-domain or cross-folder extensions via URL urldata.extensions = urldata.extensions.match(/[:\/\\]/) ? '' : urldata.extensions.split(','); } // Disallowing extension paths via URL for // security reasons, even for same-domain // ones given potential to interact in undesirable // ways with other script resources if (urldata.extPath) { delete urldata.extPath; } svgEditor.setConfig(urldata); src = urldata.source; qstr = $.param.querystring(); if (!src) { // urldata.source may have been null if it ended with '=' if (qstr.indexOf('source=data:') >= 0) { src = qstr.match(/source=(data:[^&]*)/)[1]; } } if (src) { if (src.indexOf('data:') === 0) { // plusses get replaced by spaces, so re-insert src = src.replace(/ /g, '+'); Editor.loadFromDataURI(src); } else { Editor.loadFromString(src); } } else if (qstr.indexOf('paramurl=') !== -1) { // Get parameter URL (use full length of remaining location.href) svgEditor.loadFromURL(qstr.substr(9)); } else if (urldata.url) { svgEditor.loadFromURL(urldata.url); } } else { var name = 'svgedit-' + Editor.curConfig.canvasName; var cached = window.localStorage && window.localStorage.getItem(name); if (cached) { Editor.loadFromString(cached); } } }()); var setIcon = Editor.setIcon = function(elem, icon_id, forcedSize) { var icon = (typeof icon_id === 'string') ? $.getSvgIcon(icon_id, true) : icon_id.clone(); if (!icon) { console.log('NOTE: Icon image missing: ' + icon_id); return; } $(elem).empty().append(icon); }; var extFunc = function() { $.each(curConfig.extensions, function() { var extname = this; $.getScript(curConfig.extPath + extname, function(d) { // Fails locally in Chrome 5 if (!d) { var s = document.createElement('script'); s.src = curConfig.extPath + extname; document.querySelector('head').appendChild(s); } }); }); var good_langs = []; $('#lang_select option').each(function() { good_langs.push(this.value); }); // var lang = ('lang' in curPrefs) ? curPrefs.lang : null; Editor.putLocale(null, good_langs); }; // Load extensions // Bit of a hack to run extensions in local Opera/IE9 if (document.location.protocol === 'file:') { setTimeout(extFunc, 100); } else { extFunc(); } $.svgIcons(curConfig.imgPath + 'svg_edit_icons.svg', { w:24, h:24, id_match: false, no_img: !svgedit.browser.isWebkit(), // Opera & Firefox 4 gives odd behavior w/images fallback_path: curConfig.imgPath, fallback: { 'new_image': 'clear.png', 'save': 'save.png', 'open': 'open.png', 'source': 'source.png', 'docprops': 'document-properties.png', 'wireframe': 'wireframe.png', 'undo': 'undo.png', 'redo': 'redo.png', 'select': 'select.png', 'select_node': 'select_node.png', 'pencil': 'fhpath.png', 'pen': 'line.png', 'square': 'square.png', 'rect': 'rect.png', 'fh_rect': 'freehand-square.png', 'circle': 'circle.png', 'ellipse': 'ellipse.png', 'fh_ellipse': 'freehand-circle.png', 'path': 'path.png', 'text': 'text.png', 'image': 'image.png', 'zoom': 'zoom.png', 'clone': 'clone.png', 'node_clone': 'node_clone.png', 'delete': 'delete.png', 'node_delete': 'node_delete.png', 'group': 'shape_group.png', 'ungroup': 'shape_ungroup.png', 'move_top': 'move_top.png', 'move_bottom': 'move_bottom.png', 'to_path': 'to_path.png', 'link_controls': 'link_controls.png', 'reorient': 'reorient.png', 'align_left': 'align-left.png', 'align_center': 'align-center.png', 'align_right': 'align-right.png', 'align_top': 'align-top.png', 'align_middle': 'align-middle.png', 'align_bottom': 'align-bottom.png', 'go_up': 'go-up.png', 'go_down': 'go-down.png', 'ok': 'save.png', 'cancel': 'cancel.png', 'arrow_right': 'flyouth.png', 'arrow_down': 'dropdown.gif' }, placement: { '#logo': 'logo', '#tool_clear div,#layer_new': 'new_image', '#tool_save div': 'save', '#tool_export div': 'export', '#tool_open div div': 'open', '#tool_import div div': 'import', '#tool_source': 'source', '#tool_docprops > div': 'docprops', '#tool_wireframe': 'wireframe', '#tool_undo': 'undo', '#tool_redo': 'redo', '#tool_select': 'select', '#tool_fhpath': 'pencil', '#tool_line': 'pen', '#tool_rect,#tools_rect_show': 'rect', '#tool_square': 'square', '#tool_fhrect': 'fh_rect', '#tool_ellipse,#tools_ellipse_show': 'ellipse', '#tool_circle': 'circle', '#tool_fhellipse': 'fh_ellipse', '#tool_path': 'path', '#tool_text,#layer_rename': 'text', '#tool_image': 'image', '#tool_zoom': 'zoom', '#tool_clone,#tool_clone_multi': 'clone', '#tool_node_clone': 'node_clone', '#layer_delete,#tool_delete,#tool_delete_multi': 'delete', '#tool_node_delete': 'node_delete', '#tool_add_subpath': 'add_subpath', '#tool_openclose_path': 'open_path', '#tool_move_top': 'move_top', '#tool_move_bottom': 'move_bottom', '#tool_topath': 'to_path', '#tool_node_link': 'link_controls', '#tool_reorient': 'reorient', '#tool_group': 'group', '#tool_ungroup': 'ungroup', '#tool_unlink_use': 'unlink_use', '#tool_alignleft, #tool_posleft': 'align_left', '#tool_aligncenter, #tool_poscenter': 'align_center', '#tool_alignright, #tool_posright': 'align_right', '#tool_aligntop, #tool_postop': 'align_top', '#tool_alignmiddle, #tool_posmiddle': 'align_middle', '#tool_alignbottom, #tool_posbottom': 'align_bottom', '#cur_position': 'align', '#linecap_butt,#cur_linecap': 'linecap_butt', '#linecap_round': 'linecap_round', '#linecap_square': 'linecap_square', '#linejoin_miter,#cur_linejoin': 'linejoin_miter', '#linejoin_round': 'linejoin_round', '#linejoin_bevel': 'linejoin_bevel', '#url_notice': 'warning', '#layer_up': 'go_up', '#layer_down': 'go_down', '#layer_moreopts': 'context_menu', '#layerlist td.layervis': 'eye', '#tool_source_save,#tool_docprops_save,#tool_prefs_save': 'ok', '#tool_source_cancel,#tool_docprops_cancel,#tool_prefs_cancel': 'cancel', '#rwidthLabel, #iwidthLabel': 'width', '#rheightLabel, #iheightLabel': 'height', '#cornerRadiusLabel span': 'c_radius', '#angleLabel': 'angle', '#linkLabel,#tool_make_link,#tool_make_link_multi': 'globe_link', '#zoomLabel': 'zoom', '#tool_fill label': 'fill', '#tool_stroke .icon_label': 'stroke', '#group_opacityLabel': 'opacity', '#blurLabel': 'blur', '#font_sizeLabel': 'fontsize', '.flyout_arrow_horiz': 'arrow_right', '.dropdown button, #main_button .dropdown': 'arrow_down', '#palette .palette_item:first, #fill_bg, #stroke_bg': 'no_color' }, resize: { '#logo .svg_icon': 28, '.flyout_arrow_horiz .svg_icon': 5, '.layer_button .svg_icon, #layerlist td.layervis .svg_icon': 14, '.dropdown button .svg_icon': 7, '#main_button .dropdown .svg_icon': 9, '.palette_item:first .svg_icon' : 15, '#fill_bg .svg_icon, #stroke_bg .svg_icon': 16, '.toolbar_button button .svg_icon': 16, '.stroke_tool div div .svg_icon': 20, '#tools_bottom label .svg_icon': 18 }, callback: function(icons) { $('.toolbar_button button > svg, .toolbar_button button > img').each(function() { $(this).parent().prepend(this); }); var min_height, tleft = $('#tools_left'); if (tleft.length !== 0) { min_height = tleft.offset().top + tleft.outerHeight(); } // var size = $.pref('iconsize'); // if (size && size != 'm') { // svgEditor.setIconSize(size); // } else if ($(window).height() < min_height) { // // Make smaller // svgEditor.setIconSize('s'); // } // Look for any missing flyout icons from plugins $('.tools_flyout').each(function() { var shower = $('#' + this.id + '_show'); var sel = shower.attr('data-curopt'); // Check if there's an icon here if (!shower.children('svg, img').length) { var clone = $(sel).children().clone(); if (clone.length) { clone[0].removeAttribute('style'); //Needed for Opera shower.append(clone); } } }); svgEditor.runCallbacks(); setTimeout(function() { $('.flyout_arrow_horiz:empty').each(function() { $(this).append($.getSvgIcon('arrow_right').width(5).height(5)); }); }, 1); } }); Editor.canvas = svgCanvas = new $.SvgCanvas(document.getElementById('svgcanvas'), curConfig); Editor.showSaveWarning = false; var palette = [ '#000000', '#3f3f3f', '#7f7f7f', '#bfbfbf', '#ffffff', '#ff0000', '#ff7f00', '#ffff00', '#7fff00', '#00ff00', '#00ff7f', '#00ffff', '#007fff', '#0000ff', '#7f00ff', '#ff00ff', '#ff007f', '#7f0000', '#7f3f00', '#7f7f00', '#3f7f00', '#007f00', '#007f3f', '#007f7f', '#003f7f', '#00007f', '#3f007f', '#7f007f', '#7f003f', '#ffaaaa', '#ffd4aa', '#ffffaa', '#d4ffaa', '#aaffaa', '#aaffd4', '#aaffff', '#aad4ff', '#aaaaff', '#d4aaff', '#ffaaff', '#ffaad4' ], modKey = (svgedit.browser.isMac() ? 'meta+' : 'ctrl+'), // ⌘ path = svgCanvas.pathActions, undoMgr = svgCanvas.undoMgr, Utils = svgedit.utilities, defaultImageURL = curConfig.imgPath + 'logo.png', workarea = $('#workarea'), canv_menu = $('#cmenu_canvas'), layer_menu = $('#cmenu_layers'), exportWindow = null, tool_scale = 1, zoomInIcon = 'crosshair', zoomOutIcon = 'crosshair', ui_context = 'toolbars', origSource = '', paintBox = {fill: null, stroke:null}; // This sets up alternative dialog boxes. They mostly work the same way as // their UI counterparts, expect instead of returning the result, a callback // needs to be included that returns the result as its first parameter. // In the future we may want to add additional types of dialog boxes, since // they should be easy to handle this way. (function() { $('#dialog_container').draggable({cancel: '#dialog_content, #dialog_buttons *', containment: 'window'}); var box = $('#dialog_box'), btn_holder = $('#dialog_buttons'), dialog_content = $('#dialog_content'), dbox = function(type, msg, callback, defaultVal, opts, changeCb) { var ok, ctrl; dialog_content.html('

'+msg.replace(/\n/g, '

')+'

') .toggleClass('prompt', (type == 'prompt')); btn_holder.empty(); ok = $('').appendTo(btn_holder); if (type !== 'alert') { $('') .appendTo(btn_holder) .click(function() { box.hide(); callback(false);}); } if (type === 'prompt') { ctrl = $('').prependTo(btn_holder); ctrl.val(defaultVal || ''); ctrl.bind('keydown', 'return', function() {ok.click();}); } else if (type === 'select') { var div = $('
'); ctrl = $(''; $.each(tool.options, function(val, text) { var sel = (val == tool.defval) ? " selected":""; html += ''; }); html += ""; // Creates the tool, hides & adds it, returns the select element var sel = $(html).appendTo(panel).find('select'); $.each(tool.events, function(evt, func) { $(sel).bind(evt, func); }); break; case 'button-select': html = ''; var list = $('').appendTo('#option_lists'); if (tool.colnum) { list.addClass('optcols' + tool.colnum); } // Creates the tool, hides & adds it, returns the select element var dropdown = $(html).appendTo(panel).children(); btn_selects.push({ elem: ('#' + tool.id), list: ('#' + tool.id + '_opts'), title: tool.title, callback: tool.events.change, cur: ('#cur_' + tool.id) }); break; case 'input': html = '' + '' + tool.label + ':' + ''; // Creates the tool, hides & adds it, returns the select element // Add to given tool.panel var inp = $(html).appendTo(panel).find('input'); if (tool.spindata) { inp.SpinButton(tool.spindata); } if (tool.events) { $.each(tool.events, function(evt, func) { inp.bind(evt, func); }); } break; default: break; } }); } if (ext.buttons) { var fallback_obj = {}, placement_obj = {}, svgicons = ext.svgicons, holders = {}; // Add buttons given by extension $.each(ext.buttons, function(i, btn) { var icon, svgicon, tls_id; var id = btn.id; var num = i; // Give button a unique ID while($('#'+id).length) { id = btn.id + '_' + (++num); } if (!svgicons) { icon = $(''); } else { fallback_obj[id] = btn.icon; svgicon = btn.svgicon || btn.id; if (btn.type == 'app_menu') { placement_obj['#' + id + ' > div'] = svgicon; } else { placement_obj['#' + id] = svgicon; } } var cls, parent; // Set button up according to its type switch ( btn.type ) { case 'mode_flyout': case 'mode': cls = 'tool_button'; parent = '#tools_left'; break; case 'context': cls = 'tool_button'; parent = '#' + btn.panel; // create the panel if it doesn't exist if (!$(parent).length) { $('
', {id: btn.panel}).appendTo('#tools_top'); } break; case 'app_menu': cls = ''; parent = '#main_menu ul'; break; } var flyout_holder, cur_h, show_btn, ref_data, ref_btn; var button = $((btn.list || btn.type == 'app_menu') ? '
  • ' : '
    ') .attr('id', id) .attr('title', btn.title) .addClass(cls); if (!btn.includeWith && !btn.list) { if (btn.position) { $(parent).children().eq(btn.position).before(button); } else { button.appendTo(parent); } if (btn.type =='mode_flyout') { // Add to flyout menu / make flyout menu // var opts = btn.includeWith; // // opts.button, default, position ref_btn = $(button); flyout_holder = ref_btn.parent(); // Create a flyout menu if there isn't one already if (!ref_btn.parent().hasClass('tools_flyout')) { // Create flyout placeholder tls_id = ref_btn[0].id.replace('tool_', 'tools_'); show_btn = ref_btn.clone() .attr('id',tls_id + '_show') .append($('
    ', {'class': 'flyout_arrow_horiz'})); ref_btn.before(show_btn); // Create a flyout div flyout_holder = makeFlyoutHolder(tls_id, ref_btn); flyout_holder.data('isLibrary', true); show_btn.data('isLibrary', true); } // ref_data = Actions.getButtonData(opts.button); placement_obj['#' + tls_id + '_show'] = btn.id; // TODO: Find way to set the current icon using the iconloader if this is not default // Include data for extension button as well as ref button cur_h = holders['#'+flyout_holder[0].id] = [{ sel: '#'+id, fn: btn.events.click, icon: btn.id, // key: btn.key, isDefault: true }, ref_data]; // // // {sel:'#tool_rect', fn: clickRect, evt: 'mouseup', key: 4, parent: '#tools_rect', icon: 'rect'} // // var pos = ('position' in opts)?opts.position:'last'; // var len = flyout_holder.children().length; // // // Add at given position or end // if (!isNaN(pos) && pos >= 0 && pos < len) { // flyout_holder.children().eq(pos).before(button); // } else { // flyout_holder.append(button); // cur_h.reverse(); // } } else if (btn.type == 'app_menu') { button.append('
    ').append(btn.title); } } else if (btn.list) { // Add button to list button.addClass('push_button'); $('#' + btn.list + '_opts').append(button); if (btn.isDefault) { $('#cur_' + btn.list).append(button.children().clone()); svgicon = btn.svgicon || btn.id; placement_obj['#cur_' + btn.list] = svgicon; } } else if (btn.includeWith) { // Add to flyout menu / make flyout menu var opts = btn.includeWith; // opts.button, default, position ref_btn = $(opts.button); flyout_holder = ref_btn.parent(); // Create a flyout menu if there isn't one already if (!ref_btn.parent().hasClass('tools_flyout')) { // Create flyout placeholder tls_id = ref_btn[0].id.replace('tool_', 'tools_'); show_btn = ref_btn.clone() .attr('id',tls_id + '_show') .append($('
    ', {'class': 'flyout_arrow_horiz'})); ref_btn.before(show_btn); // Create a flyout div flyout_holder = makeFlyoutHolder(tls_id, ref_btn); } ref_data = Actions.getButtonData(opts.button); if (opts.isDefault) { placement_obj['#' + tls_id + '_show'] = btn.id; } // TODO: Find way to set the current icon using the iconloader if this is not default // Include data for extension button as well as ref button cur_h = holders['#'+flyout_holder[0].id] = [{ sel: '#'+id, fn: btn.events.click, icon: btn.id, key: btn.key, isDefault: btn.includeWith?btn.includeWith.isDefault:0 }, ref_data]; // {sel:'#tool_rect', fn: clickRect, evt: 'mouseup', key: 4, parent: '#tools_rect', icon: 'rect'} var pos = opts.position || 'last'; var len = flyout_holder.children().length; // Add at given position or end if (!isNaN(pos) && pos >= 0 && pos < len) { flyout_holder.children().eq(pos).before(button); } else { flyout_holder.append(button); cur_h.reverse(); } } if (!svgicons) { button.append(icon); } if (!btn.list) { // Add given events to button $.each(btn.events, function(name, func) { if (name == 'click' && btn.type == 'mode') { if (btn.includeWith) { button.bind(name, func); } else { button.bind(name, function() { if (toolButtonClick(button)) { func(); } }); } if (btn.key) { $(document).bind('keydown', btn.key, func); if (btn.title) { button.attr('title', btn.title + ' ['+btn.key+']'); } } } else { button.bind(name, func); } }); } setupFlyouts(holders); }); $.each(btn_selects, function() { addAltDropDown(this.elem, this.list, this.callback, {seticon: true}); }); if (svgicons) { cb_ready = false; // Delay callback } $.svgIcons(svgicons, { w:24, h:24, id_match: false, no_img: (!svgedit.browser.isWebkit()), fallback: fallback_obj, placement: placement_obj, callback: function(icons) { // Non-ideal hack to make the icon match the current size if (curPrefs.iconsize && curPrefs.iconsize != 'm') { prepResize(); } cb_ready = true; // Ready for callback runCallback(); } }); } runCallback(); }; var getPaint = function(color, opac, type) { // update the editor's fill paint var opts = { alpha: opac }; if (color.indexOf('url(#') === 0) { var refElem = svgCanvas.getRefElem(color); if (refElem) { refElem = refElem.cloneNode(true); } else { refElem = $('#' + type + '_color defs *')[0]; } opts[refElem.tagName] = refElem; } else if (color.indexOf('#') === 0) { opts.solidColor = color.substr(1); } else { opts.solidColor = 'none'; } return new $.jGraduate.Paint(opts); }; $('#text').focus( function(){ textBeingEntered = true; } ); $('#text').blur( function(){ textBeingEntered = false; } ); // bind the selected event to our function that handles updates to the UI svgCanvas.bind('selected', selectedChanged); svgCanvas.bind('transition', elementTransition); svgCanvas.bind('changed', elementChanged); svgCanvas.bind('saved', saveHandler); svgCanvas.bind('exported', exportHandler); svgCanvas.bind('zoomed', zoomChanged); svgCanvas.bind('contextset', contextChanged); svgCanvas.bind('extension_added', extAdded); svgCanvas.textActions.setInputElem($('#text')[0]); var str = '
    '; $.each(palette, function(i,item) { str += '
    '; }); $('#palette').append(str); // Set up editor background functionality // TODO add checkerboard as "pattern" var color_blocks = ['#FFF', '#888', '#000']; // ,'url(data:image/gif;base64,R0lGODlhEAAQAIAAAP%2F%2F%2F9bW1iH5BAAAAAAALAAAAAAQABAAAAIfjG%2Bgq4jM3IFLJgpswNly%2FXkcBpIiVaInlLJr9FZWAQA7)']; str = ''; $.each(color_blocks, function() { str += '
    '; }); $('#bg_blocks').append(str); var blocks = $('#bg_blocks div'); var cur_bg = 'cur_background'; blocks.each(function() { var blk = $(this); blk.click(function() { blocks.removeClass(cur_bg); $(this).addClass(cur_bg); }); }); if ($.pref('bkgd_color')) { setBackground($.pref('bkgd_color'), $.pref('bkgd_url')); } else if ($.pref('bkgd_url')) { // No color set, only URL setBackground(defaultPrefs.bkgd_color, $.pref('bkgd_url')); } if ($.pref('img_save')) { curPrefs.img_save = $.pref('img_save'); $('#image_save_opts input').val([curPrefs.img_save]); } var changeRectRadius = function(ctl) { svgCanvas.setRectRadius(ctl.value); }; var changeFontSize = function(ctl) { svgCanvas.setFontSize(ctl.value); }; var changeStrokeWidth = function(ctl) { var val = ctl.value; if (val == 0 && selectedElement && ['line', 'polyline'].indexOf(selectedElement.nodeName) >= 0) { val = ctl.value = 1; } svgCanvas.setStrokeWidth(val); }; var changeRotationAngle = function(ctl) { svgCanvas.setRotationAngle(ctl.value); $('#tool_reorient').toggleClass('disabled', parseInt(ctl.value, 10) === 0); }; var changeOpacity = function(ctl, val) { if (val == null) {val = ctl.value;} $('#group_opacity').val(val); if (!ctl || !ctl.handle) { $('#opac_slider').slider('option', 'value', val); } svgCanvas.setOpacity(val/100); }; var changeBlur = function(ctl, val, noUndo) { if (val == null) {val = ctl.value;} $('#blur').val(val); var complete = false; if (!ctl || !ctl.handle) { $('#blur_slider').slider('option', 'value', val); complete = true; } if (noUndo) { svgCanvas.setBlurNoUndo(val); } else { svgCanvas.setBlur(val, complete); } }; $('#stroke_style').change(function() { svgCanvas.setStrokeAttr('stroke-dasharray', $(this).val()); operaRepaint(); }); $('#stroke_linejoin').change(function() { svgCanvas.setStrokeAttr('stroke-linejoin', $(this).val()); operaRepaint(); }); // Lose focus for select elements when changed (Allows keyboard shortcuts to work better) $('select').change(function(){$(this).blur();}); // fired when user wants to move elements to another layer var promptMoveLayerOnce = false; $('#selLayerNames').change(function() { var destLayer = this.options[this.selectedIndex].value; var confirmStr = uiStrings.notification.QmoveElemsToLayer.replace('%s', destLayer); var moveToLayer = function(ok) { if (!ok) {return;} promptMoveLayerOnce = true; svgCanvas.moveSelectedToLayer(destLayer); svgCanvas.clearSelection(); populateLayers(); }; if (destLayer) { if (promptMoveLayerOnce) { moveToLayer(true); } else { $.confirm(confirmStr, moveToLayer); } } }); $('#font_family').change(function() { svgCanvas.setFontFamily(this.value); }); $('#seg_type').change(function() { svgCanvas.setSegType($(this).val()); }); $('#text').keyup(function() { svgCanvas.setTextContent(this.value); }); $('#image_url').change(function() { setImageURL(this.value); }); $('#link_url').change(function() { if (this.value.length) { svgCanvas.setLinkURL(this.value); } else { svgCanvas.removeHyperlink(); } }); $('#g_title').change(function() { svgCanvas.setGroupTitle(this.value); }); $('.attr_changer').change(function() { var attr = this.getAttribute('data-attr'); var val = this.value; var valid = svgedit.units.isValidUnit(attr, val, selectedElement); if (!valid) { $.alert(uiStrings.notification.invalidAttrValGiven); this.value = selectedElement.getAttribute(attr); return false; } if (attr !== 'id') { if (isNaN(val)) { val = svgCanvas.convertToNum(attr, val); } else if (curConfig.baseUnit !== 'px') { // Convert unitless value to one with given unit var unitData = svgedit.units.getTypeMap(); if (selectedElement[attr] || svgCanvas.getMode() === 'pathedit' || attr === 'x' || attr === 'y') { val *= unitData[curConfig.baseUnit]; } } } // if the user is changing the id, then de-select the element first // change the ID, then re-select it with the new ID if (attr === 'id') { var elem = selectedElement; svgCanvas.clearSelection(); elem.id = val; svgCanvas.addToSelection([elem],true); } else { svgCanvas.changeSelectedAttribute(attr, val); } this.blur(); }); // Prevent selection of elements when shift-clicking $('#palette').mouseover(function() { var inp = $(''); $(this).append(inp); inp.focus().remove(); }); $('.palette_item').mousedown(function(evt) { // shift key or right click for stroke var picker = evt.shiftKey || evt.button === 2 ? 'stroke' : 'fill'; var color = $(this).data('rgb'); var paint; // Webkit-based browsers returned 'initial' here for no stroke if (color === 'none' || color === 'transparent' || color === 'initial') { color = 'none'; paint = new $.jGraduate.Paint(); } else { paint = new $.jGraduate.Paint({alpha: 100, solidColor: color.substr(1)}); } paintBox[picker].setPaint(paint); svgCanvas.setColor(picker, color); if (color !== 'none' && svgCanvas.getPaintOpacity(picker) !== 1) { svgCanvas.setPaintOpacity(picker, 1.0); } updateToolButtonState(); }).bind('contextmenu', function(e) {e.preventDefault();}); $('#toggle_stroke_tools').on('click', function() { $('#tools_bottom').toggleClass('expanded'); }); (function() { var last_x = null, last_y = null, w_area = workarea[0], panning = false, keypan = false; $('#svgcanvas').bind('mousemove mouseup', function(evt) { if (panning === false) {return;} w_area.scrollLeft -= (evt.clientX - last_x); w_area.scrollTop -= (evt.clientY - last_y); last_x = evt.clientX; last_y = evt.clientY; if (evt.type === 'mouseup') {panning = false;} return false; }).mousedown(function(evt) { if (evt.button === 1 || keypan === true) { panning = true; last_x = evt.clientX; last_y = evt.clientY; return false; } }); $(window).mouseup(function() { panning = false; }); $(document).bind('keydown', 'space', function(evt) { svgCanvas.spaceKey = keypan = true; evt.preventDefault(); }).bind('keyup', 'space', function(evt) { evt.preventDefault(); svgCanvas.spaceKey = keypan = false; }).bind('keydown', 'shift', function(evt) { if (svgCanvas.getMode() === 'zoom') { workarea.css('cursor', zoomOutIcon); } }).bind('keyup', 'shift', function(evt) { if (svgCanvas.getMode() === 'zoom') { workarea.css('cursor', zoomInIcon); } }); Editor.setPanning = function(active) { svgCanvas.spaceKey = keypan = active; }; }()); (function () { var button = $('#main_icon'); var overlay = $('#main_icon span'); var list = $('#main_menu'); var on_button = false; var height = 0; var js_hover = true; var set_click = false; var hideMenu = function() { list.fadeOut(200); }; $(window).mouseup(function(evt) { if (!on_button) { button.removeClass('buttondown'); // do not hide if it was the file input as that input needs to be visible // for its change event to fire if (evt.target.tagName != 'INPUT') { list.fadeOut(200); } else if (!set_click) { set_click = true; $(evt.target).click(function() { list.css('margin-left', '-9999px').show(); }); } } on_button = false; }).mousedown(function(evt) { // $('.contextMenu').hide(); var islib = $(evt.target).closest('div.tools_flyout, .contextMenu').length; if (!islib) {$('.tools_flyout:visible,.contextMenu').fadeOut(250);} }); overlay.bind('mousedown',function() { if (!button.hasClass('buttondown')) { // Margin must be reset in case it was changed before; list.css('margin-left', 0).show(); if (!height) { height = list.height(); } // Using custom animation as slideDown has annoying 'bounce effect' list.css('height',0).animate({ 'height': height }, 200); on_button = true; } else { list.fadeOut(200); } button.toggleClass('buttondown buttonup'); }).hover(function() { on_button = true; }).mouseout(function() { on_button = false; }); var list_items = $('#main_menu li'); // Check if JS method of hovering needs to be used (Webkit bug) list_items.mouseover(function() { js_hover = ($(this).css('background-color') == 'rgba(0, 0, 0, 0)'); list_items.unbind('mouseover'); if (js_hover) { list_items.mouseover(function() { this.style.backgroundColor = '#FFC'; }).mouseout(function() { this.style.backgroundColor = 'transparent'; return true; }); } }); }()); // Made public for UI customization. // TODO: Group UI functions into a public svgEditor.ui interface. Editor.addDropDown = function(elem, callback, dropUp) { if ($(elem).length == 0) {return;} // Quit if called on non-existant element var button = $(elem).find('button'); var list = $(elem).find('ul').attr('id', $(elem)[0].id + '-list'); var on_button = false; if (dropUp) { $(elem).addClass('dropup'); } else { // Move list to place where it can overflow container $('#option_lists').append(list); } list.find('li').bind('mouseup', callback); $(window).mouseup(function(evt) { if (!on_button) { button.removeClass('down'); list.hide(); } on_button = false; }); button.bind('mousedown',function() { if (!button.hasClass('down')) { if (!dropUp) { var pos = $(elem).position(); list.css({ top: pos.top + 24, left: pos.left - 10 }); } list.show(); on_button = true; } else { list.hide(); } button.toggleClass('down'); }).hover(function() { on_button = true; }).mouseout(function() { on_button = false; }); }; Editor.addDropDown('#font_family_dropdown', function() { $('#font_family').val($(this).text()).change(); }); Editor.addDropDown('#opacity_dropdown', function() { if ($(this).find('div').length) {return;} var perc = parseInt($(this).text().split('%')[0]); changeOpacity(false, perc); }, true); // For slider usage, see: http://jqueryui.com/demos/slider/ $('#opac_slider').slider({ start: function() { $('#opacity_dropdown li:not(.special)').hide(); }, stop: function() { $('#opacity_dropdown li').show(); $(window).mouseup(); }, slide: function(evt, ui) { changeOpacity(ui); } }); Editor.addDropDown('#blur_dropdown', $.noop); var slideStart = false; $('#blur_slider').slider({ max: 10, step: 0.1, stop: function(evt, ui) { slideStart = false; changeBlur(ui); $('#blur_dropdown li').show(); $(window).mouseup(); }, start: function() { slideStart = true; }, slide: function(evt, ui) { changeBlur(ui, null, slideStart); } }); Editor.addDropDown('#zoom_dropdown', function() { var item = $(this); var val = item.data('val'); if (val) { zoomChanged(window, val); } else { changeZoom({value: parseInt(item.text(), 10)}); } }, true); addAltDropDown('#stroke_linecap', '#linecap_opts', function() { setStrokeOpt(this, true); }, {dropUp: true}); addAltDropDown('#stroke_linejoin', '#linejoin_opts', function() { setStrokeOpt(this, true); }, {dropUp: true}); addAltDropDown('#tool_position', '#position_opts', function() { var letter = this.id.replace('tool_pos', '').charAt(0); svgCanvas.alignSelectedElements(letter, 'page'); }, {multiclick: true}); /* When a flyout icon is selected (if flyout) { - Change the icon - Make pressing the button run its stuff } - Run its stuff When its shortcut key is pressed - If not current in list, do as above , else: - Just run its stuff */ // Unfocus text input when workarea is mousedowned. (function() { var inp; var unfocus = function() { $(inp).blur(); }; $('#svg_editor').find('button, select, input:not(#text)').focus(function() { inp = this; ui_context = 'toolbars'; workarea.mousedown(unfocus); }).blur(function() { ui_context = 'canvas'; workarea.unbind('mousedown', unfocus); // Go back to selecting text if in textedit mode if (svgCanvas.getMode() == 'textedit') { $('#text').focus(); } }); }()); var clickFHPath = function() { if (toolButtonClick('#tool_fhpath')) { svgCanvas.setMode('fhpath'); } }; var clickLine = function() { if (toolButtonClick('#tool_line')) { svgCanvas.setMode('line'); } }; var clickSquare = function() { if (toolButtonClick('#tool_square')) { svgCanvas.setMode('square'); } }; var clickRect = function() { if (toolButtonClick('#tool_rect')) { svgCanvas.setMode('rect'); } }; var clickFHRect = function() { if (toolButtonClick('#tool_fhrect')) { svgCanvas.setMode('fhrect'); } }; var clickCircle = function() { if (toolButtonClick('#tool_circle')) { svgCanvas.setMode('circle'); } }; var clickEllipse = function() { if (toolButtonClick('#tool_ellipse')) { svgCanvas.setMode('ellipse'); } }; var clickFHEllipse = function() { if (toolButtonClick('#tool_fhellipse')) { svgCanvas.setMode('fhellipse'); } }; var clickImage = function() { if (toolButtonClick('#tool_image')) { svgCanvas.setMode('image'); } }; var clickZoom = function() { if (toolButtonClick('#tool_zoom')) { svgCanvas.setMode('zoom'); workarea.css('cursor', zoomInIcon); } }; var zoomImage = function(multiplier) { var res = svgCanvas.getResolution(); multiplier = multiplier ? res.zoom * multiplier : 1; // setResolution(res.w * multiplier, res.h * multiplier, true); $('#zoom').val(multiplier * 100); svgCanvas.setZoom(multiplier); zoomDone(); updateCanvas(true); }; var dblclickZoom = function() { if (toolButtonClick('#tool_zoom')) { zoomImage(); setSelectMode(); } }; var clickText = function() { if (toolButtonClick('#tool_text')) { svgCanvas.setMode('text'); } }; var clickPath = function() { if (toolButtonClick('#tool_path')) { svgCanvas.setMode('path'); } }; // Delete is a contextual tool that only appears in the ribbon if // an element has been selected var deleteSelected = function() { if (selectedElement != null || multiselected) { svgCanvas.deleteSelectedElements(); } }; var cutSelected = function() { if (selectedElement != null || multiselected) { svgCanvas.cutSelectedElements(); } }; var copySelected = function() { if (selectedElement != null || multiselected) { svgCanvas.copySelectedElements(); } }; var pasteInCenter = function() { var zoom = svgCanvas.getZoom(); var x = (workarea[0].scrollLeft + workarea.width()/2)/zoom - svgCanvas.contentW; var y = (workarea[0].scrollTop + workarea.height()/2)/zoom - svgCanvas.contentH; svgCanvas.pasteElements('point', x, y); }; var moveToTopSelected = function() { if (selectedElement != null) { svgCanvas.moveToTopSelectedElement(); } }; var moveToBottomSelected = function() { if (selectedElement != null) { svgCanvas.moveToBottomSelectedElement(); } }; var moveUpDownSelected = function(dir) { if (selectedElement != null) { svgCanvas.moveUpDownSelected(dir); } }; var convertToPath = function() { if (selectedElement != null) { svgCanvas.convertToPath(); } }; var reorientPath = function() { if (selectedElement != null) { path.reorient(); } }; var makeHyperlink = function() { if (selectedElement != null || multiselected) { $.prompt(uiStrings.notification.enterNewLinkURL, 'http://', function(url) { if (url) {svgCanvas.makeHyperlink(url);} }); } }; var moveSelected = function(dx,dy) { if (selectedElement != null || multiselected) { if (curConfig.gridSnapping) { // Use grid snap value regardless of zoom level var multi = svgCanvas.getZoom() * curConfig.snappingStep; dx *= multi; dy *= multi; } svgCanvas.moveSelectedElements(dx,dy); } }; var linkControlPoints = function() { $('#tool_node_link').toggleClass('push_button_pressed tool_button'); path.linkControlPoints(linked); }; var clonePathNode = function() { if (path.getNodePoint()) { path.clonePathNode(); } }; var deletePathNode = function() { if (path.getNodePoint()) { path.deletePathNode(); } }; var addSubPath = function() { button.toggleClass('push_button_pressed tool_button'); path.addSubPath(sp); }; var opencloseSubPath = function() { path.opencloseSubPath(); }; var selectNext = function() { svgCanvas.cycleElement(1); }; var selectPrev = function() { svgCanvas.cycleElement(0); }; var rotateSelected = function(cw, step) { if (selectedElement == null || multiselected) {return;} if (!cw) {step *= -1;} var angle = parseFloat($('#angle').val()) + step; svgCanvas.setRotationAngle(angle); updateContextPanel(); }; var clickClear = function() { var dims = curConfig.dimensions; $.confirm(uiStrings.notification.QwantToClear, function(ok) { if (!ok) {return;} setSelectMode(); svgCanvas.clear(); svgCanvas.setResolution(dims[0], dims[1]); updateCanvas(true); zoomImage(); populateLayers(); updateContextPanel(); prepPaints(); svgCanvas.runExtensions('onNewDocument'); }); }; var clickBold = function() { svgCanvas.setBold( !svgCanvas.getBold() ); updateContextPanel(); return false; }; var clickItalic = function() { svgCanvas.setItalic( !svgCanvas.getItalic() ); updateContextPanel(); return false; }; var clickSave = function() { // In the future, more options can be provided here var saveOpts = { 'images': curPrefs.img_save, 'round_digits': 6 }; svgCanvas.save(saveOpts); }; var clickExport = function() { $.select('Select an image type for export: ', [ // See http://kangax.github.io/jstests/toDataUrl_mime_type_test/ for a useful list of MIME types and browser support // 'ICO', // Todo: Find a way to preserve transparency in SVG-Edit if not working presently and do full packaging for x-icon; then switch back to position after 'PNG' 'PNG', 'JPEG', 'BMP', 'WEBP' ], function (imgType) { // todo: replace hard-coded msg with uiStrings.notification. if (!imgType) { return; } // Open placeholder window (prevents popup) if (!customHandlers.exportImage && !customHandlers.pngsave) { var str = uiStrings.notification.loadingImage; exportWindow = window.open('data:text/html;charset=utf-8,' + str + '<\/title><h1>' + str + '<\/h1>'); } var quality = parseInt($('#image-slider').val(), 10)/100; if (window.canvg) { svgCanvas.rasterExport(imgType, quality); } else { $.getScript('canvg/rgbcolor.js', function() { $.getScript('canvg/canvg.js', function() { svgCanvas.rasterExport(imgType, quality); }); }); } }, function () { var sel = $(this); if (sel.val() === 'JPEG' || sel.val() === 'WEBP') { if (!$('#image-slider').length) { $('<div><label>Quality: <input id="image-slider" type="range" min="1" max="100" value="92" /></label></div>').appendTo(sel.parent()); // Todo: i18n-ize label } } else { $('#image-slider').parent().remove(); } }); }; // by default, svgCanvas.open() is a no-op. // it is up to an extension mechanism (opera widget, etc) // to call setCustomHandlers() which will make it do something var clickOpen = function() { svgCanvas.open(); }; var clickImport = function() { }; var clickUndo = function() { if (undoMgr.getUndoStackSize() > 0) { undoMgr.undo(); populateLayers(); } }; var clickRedo = function() { if (undoMgr.getRedoStackSize() > 0) { undoMgr.redo(); populateLayers(); } }; var clickGroup = function() { // group if (multiselected) { svgCanvas.groupSelectedElements(); } // ungroup else if (selectedElement) { svgCanvas.ungroupSelectedElement(); } }; var clickClone = function() { svgCanvas.cloneSelectedElements(20, 20); }; var clickAlign = function() { var letter = this.id.replace('tool_align', '').charAt(0); svgCanvas.alignSelectedElements(letter, $('#align_relative_to').val()); }; var clickWireframe = function() { $('#tool_wireframe').toggleClass('push_button_pressed tool_button'); workarea.toggleClass('wireframe'); if (supportsNonSS) {return;} var wf_rules = $('#wireframe_rules'); if (!wf_rules.length) { wf_rules = $('<style id="wireframe_rules"><\/style>').appendTo('head'); } else { wf_rules.empty(); } updateWireFrame(); }; $('#svg_docprops_container, #svg_prefs_container').draggable({cancel: 'button,fieldset', containment: 'window'}); var showDocProperties = function() { if (docprops) {return;} docprops = true; // This selects the correct radio button by using the array notation $('#image_save_opts input').val([curPrefs.img_save]); // update resolution option with actual resolution var res = svgCanvas.getResolution(); if (curConfig.baseUnit !== 'px') { res.w = svgedit.units.convertUnit(res.w) + curConfig.baseUnit; res.h = svgedit.units.convertUnit(res.h) + curConfig.baseUnit; } $('#canvas_width').val(res.w); $('#canvas_height').val(res.h); $('#canvas_title').val(svgCanvas.getDocumentTitle()); $('#svg_docprops').show(); }; var showPreferences = function() { if (preferences) {return;} preferences = true; $('#main_menu').hide(); // Update background color with current one var blocks = $('#bg_blocks div'); var cur_bg = 'cur_background'; var canvas_bg = $.pref('bkgd_color'); var url = $.pref('bkgd_url'); // if (url) url = url[1]; blocks.each(function() { var blk = $(this); var is_bg = blk.css('background-color') == canvas_bg; blk.toggleClass(cur_bg, is_bg); if (is_bg) {$('#canvas_bg_url').removeClass(cur_bg);} }); if (!canvas_bg) {blocks.eq(0).addClass(cur_bg);} if (url) { $('#canvas_bg_url').val(url); } $('#grid_snapping_on').prop('checked', curConfig.gridSnapping); $('#grid_snapping_step').attr('value', curConfig.snappingStep); $('#grid_color').attr('value', curConfig.gridColor); $('#svg_prefs').show(); }; var hideSourceEditor = function() { $('#svg_source_editor').hide(); editingsource = false; $('#svg_source_textarea').blur(); }; var saveSourceEditor = function() { if (!editingsource) {return;} var saveChanges = function() { svgCanvas.clearSelection(); hideSourceEditor(); zoomImage(); populateLayers(); updateTitle(); prepPaints(); }; if (!svgCanvas.setSvgString($('#svg_source_textarea').val())) { $.confirm(uiStrings.notification.QerrorsRevertToSource, function(ok) { if (!ok) {return false;} saveChanges(); }); } else { saveChanges(); } setSelectMode(); }; var hideDocProperties = function() { $('#svg_docprops').hide(); $('#canvas_width,#canvas_height').removeAttr('disabled'); $('#resolution')[0].selectedIndex = 0; $('#image_save_opts input').val([curPrefs.img_save]); docprops = false; }; var hidePreferences = function() { $('#svg_prefs').hide(); preferences = false; }; var saveDocProperties = function() { // set title var newTitle = $('#canvas_title').val(); updateTitle(newTitle); svgCanvas.setDocumentTitle(newTitle); // update resolution var width = $('#canvas_width'), w = width.val(); var height = $('#canvas_height'), h = height.val(); if (w != 'fit' && !svgedit.units.isValidUnit('width', w)) { $.alert(uiStrings.notification.invalidAttrValGiven); width.parent().addClass('error'); return false; } width.parent().removeClass('error'); if (h != 'fit' && !svgedit.units.isValidUnit('height', h)) { $.alert(uiStrings.notification.invalidAttrValGiven); height.parent().addClass('error'); return false; } height.parent().removeClass('error'); if (!svgCanvas.setResolution(w, h)) { $.alert(uiStrings.notification.noContentToFitTo); return false; } // set image save option curPrefs.img_save = $('#image_save_opts :checked').val(); $.pref('img_save',curPrefs.img_save); updateCanvas(); hideDocProperties(); }; var savePreferences = function() { // set background var color = $('#bg_blocks div.cur_background').css('background-color') || '#FFF'; setBackground(color, $('#canvas_bg_url').val()); // set language var lang = $('#lang_select').val(); if (lang != curPrefs.lang) { Editor.putLocale(lang); } // set icon size setIconSize($('#iconsize').val()); // set grid setting curConfig.gridSnapping = $('#grid_snapping_on')[0].checked; curConfig.snappingStep = $('#grid_snapping_step').val(); curConfig.gridColor = $('#grid_color').val(); curConfig.showRulers = $('#show_rulers')[0].checked; $('#rulers').toggle(curConfig.showRulers); if (curConfig.showRulers) {updateRulers();} curConfig.baseUnit = $('#base_unit').val(); svgCanvas.setConfig(curConfig); updateCanvas(); hidePreferences(); }; var uaPrefix = (function() { var prop; var regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/; var someScript = document.getElementsByTagName('script')[0]; for (prop in someScript.style) { if (regex.test(prop)) { // test is faster than match, so it's better to perform // that on the lot and match only when necessary return prop.match(regex)[0]; } } // Nothing found so far? if (someScript.style.WebkitOpacity) {return 'Webkit';} if (someScript.style.KhtmlOpacity) {return 'Khtml';} return ''; }()); var resetScrollPos = $.noop; var cancelOverlays = function() { $('#dialog_box').hide(); if (!editingsource && !docprops && !preferences) { if (cur_context) { svgCanvas.leaveContext(); } return; } if (editingsource) { if (origSource !== $('#svg_source_textarea').val()) { $.confirm(uiStrings.notification.QignoreSourceChanges, function(ok) { if (ok) {hideSourceEditor();} }); } else { hideSourceEditor(); } } else if (docprops) { hideDocProperties(); } else if (preferences) { hidePreferences(); } resetScrollPos(); }; var win_wh = {width:$(window).width(), height:$(window).height()}; var curScrollPos; // Fix for Issue 781: Drawing area jumps to top-left corner on window resize (IE9) if (svgedit.browser.isIE()) { (function() { resetScrollPos = function() { if (workarea[0].scrollLeft === 0 && workarea[0].scrollTop === 0) { workarea[0].scrollLeft = curScrollPos.left; workarea[0].scrollTop = curScrollPos.top; } }; curScrollPos = { left: workarea[0].scrollLeft, top: workarea[0].scrollTop }; $(window).resize(resetScrollPos); svgEditor.ready(function() { // TODO: Find better way to detect when to do this to minimize // flickering effect setTimeout(function() { resetScrollPos(); }, 500); }); workarea.scroll(function() { curScrollPos = { left: workarea[0].scrollLeft, top: workarea[0].scrollTop }; }); }()); } $(window).resize(function(evt) { $.each(win_wh, function(type, val) { var curval = $(window)[type](); workarea[0]['scroll' + (type === 'width' ? 'Left' : 'Top')] -= (curval - val)/2; win_wh[type] = curval; }); setFlyoutPositions(); }); (function() { workarea.scroll(function() { // TODO: jQuery's scrollLeft/Top() wouldn't require a null check if ($('#ruler_x').length != 0) { $('#ruler_x')[0].scrollLeft = workarea[0].scrollLeft; } if ($('#ruler_y').length != 0) { $('#ruler_y')[0].scrollTop = workarea[0].scrollTop; } }); }()); $('#url_notice').click(function() { $.alert(this.title); }); $('#change_image_url').click(promptImgURL); // added these event handlers for all the push buttons so they // behave more like buttons being pressed-in and not images (function() { var toolnames = ['clear', 'open', 'save', 'source', 'delete', 'delete_multi', 'paste', 'clone', 'clone_multi', 'move_top', 'move_bottom']; var all_tools = ''; var cur_class = 'tool_button_current'; $.each(toolnames, function(i,item) { all_tools += '#tool_' + item + (i == toolnames.length-1 ? ',' : ''); }); $(all_tools).mousedown(function() { $(this).addClass(cur_class); }).bind('mousedown mouseout', function() { $(this).removeClass(cur_class); }); $('#tool_undo, #tool_redo').mousedown(function() { if (!$(this).hasClass('disabled')) {$(this).addClass(cur_class);} }).bind('mousedown mouseout',function() { $(this).removeClass(cur_class);} ); }()); // switch modifier key in tooltips if mac // NOTE: This code is not used yet until I can figure out how to successfully bind ctrl/meta // in Opera and Chrome if (svgedit.browser.isMac() && !window.opera) { var shortcutButtons = ['tool_clear', 'tool_save', 'tool_source', 'tool_undo', 'tool_redo', 'tool_clone']; i = shortcutButtons.length; while (i--) { var button = document.getElementById(shortcutButtons[i]); if (button) { var title = button.title; var index = title.indexOf('Ctrl+'); button.title = [title.substr(0, index), 'Cmd+', title.substr(index + 5)].join(''); } } } // TODO: go back to the color boxes having white background-color and then setting // background-image to none.png (otherwise partially transparent gradients look weird) var colorPicker = function(elem) { var picker = elem.attr('id') == 'stroke_color' ? 'stroke' : 'fill'; // var opacity = (picker == 'stroke' ? $('#stroke_opacity') : $('#fill_opacity')); var paint = paintBox[picker].paint; var title = (picker == 'stroke' ? 'Pick a Stroke Paint and Opacity' : 'Pick a Fill Paint and Opacity'); var was_none = false; var pos = elem.offset(); $('#color_picker') .draggable({cancel: '.jGraduate_tabs, .jGraduate_colPick, .jGraduate_gradPick, .jPicker', containment: 'window'}) .css(curConfig.colorPickerCSS || {'left': pos.left-140, 'bottom': 40}) .jGraduate( { paint: paint, window: { pickerTitle: title }, images: { clientPath: curConfig.jGraduatePath }, newstop: 'inverse' }, function(p) { paint = new $.jGraduate.Paint(p); paintBox[picker].setPaint(paint); svgCanvas.setPaint(picker, paint); $('#color_picker').hide(); }, function() { $('#color_picker').hide(); }); }; var PaintBox = function(container, type) { var paintColor, paintOpacity, cur = curConfig[type === 'fill' ? 'initFill' : 'initStroke']; // set up gradients to be used for the buttons var svgdocbox = new DOMParser().parseFromString( '<svg xmlns="http://www.w3.org/2000/svg"><rect width="16.5" height="16.5"'+ ' fill="#' + cur.color + '" opacity="' + cur.opacity + '"/>'+ ' <defs><linearGradient id="gradbox_"/></defs></svg>', 'text/xml'); var docElem = svgdocbox.documentElement; docElem = $(container)[0].appendChild(document.importNode(docElem, true)); docElem.setAttribute('width',16.5); this.rect = docElem.firstChild; this.defs = docElem.getElementsByTagName('defs')[0]; this.grad = this.defs.firstChild; this.paint = new $.jGraduate.Paint({solidColor: cur.color}); this.type = type; this.setPaint = function(paint, apply) { this.paint = paint; var fillAttr = 'none'; var ptype = paint.type; var opac = paint.alpha / 100; switch ( ptype ) { case 'solidColor': fillAttr = (paint[ptype] != 'none') ? '#' + paint[ptype] : paint[ptype]; break; case 'linearGradient': case 'radialGradient': this.defs.removeChild(this.grad); this.grad = this.defs.appendChild(paint[ptype]); var id = this.grad.id = 'gradbox_' + this.type; fillAttr = 'url(#' + id + ')'; break; } this.rect.setAttribute('fill', fillAttr); this.rect.setAttribute('opacity', opac); if (apply) { svgCanvas.setColor(this.type, paintColor, true); svgCanvas.setPaintOpacity(this.type, paintOpacity, true); } }; this.update = function(apply) { if (!selectedElement) {return;} var i, len; var type = this.type; switch (selectedElement.tagName) { case 'use': case 'image': case 'foreignObject': // These elements don't have fill or stroke, so don't change // the current value return; case 'g': case 'a': var gPaint = null; var childs = selectedElement.getElementsByTagName('*'); for (i = 0, len = childs.length; i < len; i++) { var elem = childs[i]; var p = elem.getAttribute(type); if (i === 0) { gPaint = p; } else if (gPaint !== p) { gPaint = null; break; } } if (gPaint === null) { // No common color, don't update anything paintColor = null; return; } paintColor = gPaint; paintOpacity = 1; break; default: paintOpacity = parseFloat(selectedElement.getAttribute(type + '-opacity')); if (isNaN(paintOpacity)) { paintOpacity = 1.0; } var defColor = type === 'fill' ? 'black' : 'none'; paintColor = selectedElement.getAttribute(type) || defColor; } if (apply) { svgCanvas.setColor(type, paintColor, true); svgCanvas.setPaintOpacity(type, paintOpacity, true); } paintOpacity *= 100; var paint = getPaint(paintColor, paintOpacity, type); // update the rect inside #fill_color/#stroke_color this.setPaint(paint); }; this.prep = function() { var ptype = this.paint.type; switch ( ptype ) { case 'linearGradient': case 'radialGradient': var paint = new $.jGraduate.Paint({copy: this.paint}); svgCanvas.setPaint(type, paint); break; } }; }; paintBox.fill = new PaintBox('#fill_color', 'fill'); paintBox.stroke = new PaintBox('#stroke_color', 'stroke'); $('#stroke_width').val(curConfig.initStroke.width); $('#group_opacity').val(curConfig.initOpacity * 100); // Use this SVG elem to test vectorEffect support var testEl = paintBox.fill.rect.cloneNode(false); testEl.setAttribute('style', 'vector-effect:non-scaling-stroke'); supportsNonSS = (testEl.style.vectorEffect === 'non-scaling-stroke'); testEl.removeAttribute('style'); var svgdocbox = paintBox.fill.rect.ownerDocument; // Use this to test support for blur element. Seems to work to test support in Webkit var blurTest = svgdocbox.createElementNS(svgedit.NS.SVG, 'feGaussianBlur'); if (blurTest.stdDeviationX === undefined) { $('#tool_blur').hide(); } $(blurTest).remove(); // Test for zoom icon support (function() { var pre = '-' + uaPrefix.toLowerCase() + '-zoom-'; var zoom = pre + 'in'; workarea.css('cursor', zoom); if (workarea.css('cursor') === zoom) { zoomInIcon = zoom; zoomOutIcon = pre + 'out'; } workarea.css('cursor', 'auto'); }()); // Test for embedImage support (use timeout to not interfere with page load) setTimeout(function() { svgCanvas.embedImage('images/logo.png', function(datauri) { if (!datauri) { // Disable option $('#image_save_opts [value=embed]').attr('disabled', 'disabled'); $('#image_save_opts input').val(['ref']); curPrefs.img_save = 'ref'; $('#image_opt_embed').css('color', '#666').attr('title', uiStrings.notification.featNotSupported); } }); }, 1000); $('#fill_color, #tool_fill .icon_label').click(function() { colorPicker($('#fill_color')); updateToolButtonState(); }); $('#stroke_color, #tool_stroke .icon_label').click(function() { colorPicker($('#stroke_color')); updateToolButtonState(); }); $('#group_opacityLabel').click(function() { $('#opacity_dropdown button').mousedown(); $(window).mouseup(); }); $('#zoomLabel').click(function() { $('#zoom_dropdown button').mousedown(); $(window).mouseup(); }); $('#tool_move_top').mousedown(function(evt) { $('#tools_stacking').show(); evt.preventDefault(); }); $('.layer_button').mousedown(function() { $(this).addClass('layer_buttonpressed'); }).mouseout(function() { $(this).removeClass('layer_buttonpressed'); }).mouseup(function() { $(this).removeClass('layer_buttonpressed'); }); $('.push_button').mousedown(function() { if (!$(this).hasClass('disabled')) { $(this).addClass('push_button_pressed').removeClass('push_button'); } }).mouseout(function() { $(this).removeClass('push_button_pressed').addClass('push_button'); }).mouseup(function() { $(this).removeClass('push_button_pressed').addClass('push_button'); }); // ask for a layer name $('#layer_new').click(function() { var uniqName, i = svgCanvas.getCurrentDrawing().getNumLayers(); do { uniqName = uiStrings.layers.layer + ' ' + (++i); } while(svgCanvas.getCurrentDrawing().hasLayer(uniqName)); $.prompt(uiStrings.notification.enterUniqueLayerName, uniqName, function(newName) { if (!newName) {return;} if (svgCanvas.getCurrentDrawing().hasLayer(newName)) { $.alert(uiStrings.notification.dupeLayerName); return; } svgCanvas.createLayer(newName); updateContextPanel(); populateLayers(); }); }); function deleteLayer() { if (svgCanvas.deleteCurrentLayer()) { updateContextPanel(); populateLayers(); // This matches what SvgCanvas does // TODO: make this behavior less brittle (svg-editor should get which // layer is selected from the canvas and then select that one in the UI) $('#layerlist tr.layer').removeClass('layersel'); $('#layerlist tr.layer:first').addClass('layersel'); } } function cloneLayer() { var name = svgCanvas.getCurrentDrawing().getCurrentLayerName() + ' copy'; $.prompt(uiStrings.notification.enterUniqueLayerName, name, function(newName) { if (!newName) {return;} if (svgCanvas.getCurrentDrawing().hasLayer(newName)) { $.alert(uiStrings.notification.dupeLayerName); return; } svgCanvas.cloneLayer(newName); updateContextPanel(); populateLayers(); }); } function mergeLayer() { if ($('#layerlist tr.layersel').index() == svgCanvas.getCurrentDrawing().getNumLayers()-1) { return; } svgCanvas.mergeLayer(); updateContextPanel(); populateLayers(); } function moveLayer(pos) { var curIndex = $('#layerlist tr.layersel').index(); var total = svgCanvas.getCurrentDrawing().getNumLayers(); if (curIndex > 0 || curIndex < total-1) { curIndex += pos; svgCanvas.setCurrentLayerPosition(total-curIndex-1); populateLayers(); } } $('#layer_delete').click(deleteLayer); $('#layer_up').click(function() { moveLayer(-1); }); $('#layer_down').click(function() { moveLayer(1); }); $('#layer_rename').click(function() { var curIndex = $('#layerlist tr.layersel').prevAll().length; var oldName = $('#layerlist tr.layersel td.layername').text(); $.prompt(uiStrings.notification.enterNewLayerName, '', function(newName) { if (!newName) {return;} if (oldName == newName || svgCanvas.getCurrentDrawing().hasLayer(newName)) { $.alert(uiStrings.notification.layerHasThatName); return; } svgCanvas.renameCurrentLayer(newName); populateLayers(); }); }); var SIDEPANEL_MAXWIDTH = 300; var SIDEPANEL_OPENWIDTH = 150; var sidedrag = -1, sidedragging = false, allowmove = false; var changeSidePanelWidth = function(delta) { var rulerX = $('#ruler_x'); $('#sidepanels').width('+=' + delta); $('#layerpanel').width('+=' + delta); rulerX.css('right', parseInt(rulerX.css('right'), 10) + delta); workarea.css('right', parseInt(workarea.css('right'), 10) + delta); svgCanvas.runExtensions("workareaResized"); }; var resizeSidePanel = function(evt) { if (!allowmove) {return;} if (sidedrag == -1) {return;} sidedragging = true; var deltaX = sidedrag - evt.pageX; var sideWidth = $('#sidepanels').width(); if (sideWidth + deltaX > SIDEPANEL_MAXWIDTH) { deltaX = SIDEPANEL_MAXWIDTH - sideWidth; sideWidth = SIDEPANEL_MAXWIDTH; } else if (sideWidth + deltaX < 2) { deltaX = 2 - sideWidth; sideWidth = 2; } if (deltaX == 0) {return;} sidedrag -= deltaX; changeSidePanelWidth(deltaX); }; // if width is non-zero, then fully close it, otherwise fully open it // the optional close argument forces the side panel closed var toggleSidePanel = function(close) { var w = $('#sidepanels').width(); var deltaX = (w > 2 || close ? 2 : SIDEPANEL_OPENWIDTH) - w; changeSidePanelWidth(deltaX); }; $('#sidepanel_handle') .mousedown(function(evt) { sidedrag = evt.pageX; $(window).mousemove(resizeSidePanel); allowmove = false; // Silly hack for Chrome, which always runs mousemove right after mousedown setTimeout(function() { allowmove = true; }, 20); }) .mouseup(function(evt) { if (!sidedragging) {toggleSidePanel();} sidedrag = -1; sidedragging = false; }); $(window).mouseup(function() { sidedrag = -1; sidedragging = false; $('#svg_editor').unbind('mousemove', resizeSidePanel); }); populateLayers(); // function changeResolution(x,y) { // var zoom = svgCanvas.getResolution().zoom; // setResolution(x * zoom, y * zoom); // } var centerCanvas = function() { // this centers the canvas vertically in the workarea (horizontal handled in CSS) workarea.css('line-height', workarea.height() + 'px'); }; $(window).bind('load resize', centerCanvas); function stepFontSize(elem, step) { var orig_val = Number(elem.value); var sug_val = orig_val + step; var increasing = sug_val >= orig_val; if (step === 0) {return orig_val;} if (orig_val >= 24) { if (increasing) { return Math.round(orig_val * 1.1); } return Math.round(orig_val / 1.1); } if (orig_val <= 1) { if (increasing) { return orig_val * 2; } return orig_val / 2; } return sug_val; } function stepZoom(elem, step) { var orig_val = Number(elem.value); if (orig_val === 0) {return 100;} var sug_val = orig_val + step; if (step === 0) {return orig_val;} if (orig_val >= 100) { return sug_val; } if (sug_val >= orig_val) { return orig_val * 2; } return orig_val / 2; } // function setResolution(w, h, center) { // updateCanvas(); // // w-=0; h-=0; // // $('#svgcanvas').css( { 'width': w, 'height': h } ); // // $('#canvas_width').val(w); // // $('#canvas_height').val(h); // // // // if (center) { // // var w_area = workarea; // // var scroll_y = h/2 - w_area.height()/2; // // var scroll_x = w/2 - w_area.width()/2; // // w_area[0].scrollTop = scroll_y; // // w_area[0].scrollLeft = scroll_x; // // } // } $('#resolution').change(function() { var wh = $('#canvas_width,#canvas_height'); if (!this.selectedIndex) { if ($('#canvas_width').val() == 'fit') { wh.removeAttr('disabled').val(100); } } else if (this.value == 'content') { wh.val('fit').attr('disabled', 'disabled'); } else { var dims = this.value.split('x'); $('#canvas_width').val(dims[0]); $('#canvas_height').val(dims[1]); wh.removeAttr('disabled'); } }); //Prevent browser from erroneously repopulating fields $('input,select').attr('autocomplete', 'off'); // Associate all button actions as well as non-button keyboard shortcuts var Actions = (function() { // sel:'selector', fn:function, evt:'event', key:[key, preventDefault, NoDisableInInput] var tool_buttons = [ {sel: '#tool_select', fn: clickSelect, evt: 'click', key: ['V', true]}, {sel: '#tool_fhpath', fn: clickFHPath, evt: 'click', key: ['Q', true]}, {sel: '#tool_line', fn: clickLine, evt: 'click', key: ['L', true]}, {sel: '#tool_rect', fn: clickRect, evt: 'mouseup', key: ['R', true], parent: '#tools_rect', icon: 'rect'}, {sel: '#tool_square', fn: clickSquare, evt: 'mouseup', parent: '#tools_rect', icon: 'square'}, {sel: '#tool_fhrect', fn: clickFHRect, evt: 'mouseup', parent: '#tools_rect', icon: 'fh_rect'}, {sel: '#tool_ellipse', fn: clickEllipse, evt: 'mouseup', key: ['E', true], parent: '#tools_ellipse', icon: 'ellipse'}, {sel: '#tool_circle', fn: clickCircle, evt: 'mouseup', parent: '#tools_ellipse', icon: 'circle'}, {sel: '#tool_fhellipse', fn: clickFHEllipse, evt: 'mouseup', parent: '#tools_ellipse', icon: 'fh_ellipse'}, {sel: '#tool_path', fn: clickPath, evt: 'click', key: ['P', true]}, {sel: '#tool_text', fn: clickText, evt: 'click', key: ['T', true]}, {sel: '#tool_image', fn: clickImage, evt: 'mouseup'}, {sel: '#tool_zoom', fn: clickZoom, evt: 'mouseup', key: ['Z', true]}, {sel: '#tool_clear', fn: clickClear, evt: 'mouseup', key: ['N', true]}, {sel: '#tool_save', fn: function() { if (editingsource) { saveSourceEditor(); } else { clickSave(); } }, evt: 'mouseup', key: ['S', true]}, {sel: '#tool_export', fn: clickExport, evt: 'mouseup'}, {sel: '#tool_open', fn: clickOpen, evt: 'mouseup', key: ['O', true]}, {sel: '#tool_import', fn: clickImport, evt: 'mouseup'}, {sel: '#tool_source', fn: showSourceEditor, evt: 'click', key: ['U', true]}, {sel: '#tool_wireframe', fn: clickWireframe, evt: 'click', key: ['F', true]}, {sel: '#tool_source_cancel,.overlay,#tool_docprops_cancel,#tool_prefs_cancel', fn: cancelOverlays, evt: 'click', key: ['esc', false, false], hidekey: true}, {sel: '#tool_source_save', fn: saveSourceEditor, evt: 'click'}, {sel: '#tool_docprops_save', fn: saveDocProperties, evt: 'click'}, {sel: '#tool_docprops', fn: showDocProperties, evt: 'mouseup'}, {sel: '#tool_prefs_save', fn: savePreferences, evt: 'click'}, {sel: '#tool_prefs_option', fn: function() {showPreferences(); return false;}, evt: 'mouseup'}, {sel: '#tool_delete,#tool_delete_multi', fn: deleteSelected, evt: 'click', key: ['del/backspace', true]}, {sel: '#tool_reorient', fn: reorientPath, evt: 'click'}, {sel: '#tool_node_link', fn: linkControlPoints, evt: 'click'}, {sel: '#tool_node_clone', fn: clonePathNode, evt: 'click'}, {sel: '#tool_node_delete', fn: deletePathNode, evt: 'click'}, {sel: '#tool_openclose_path', fn: opencloseSubPath, evt: 'click'}, {sel: '#tool_add_subpath', fn: addSubPath, evt: 'click'}, {sel: '#tool_move_top', fn: moveToTopSelected, evt: 'click', key: 'ctrl+shift+]'}, {sel: '#tool_move_bottom', fn: moveToBottomSelected, evt: 'click', key: 'ctrl+shift+['}, {sel: '#tool_topath', fn: convertToPath, evt: 'click'}, {sel: '#tool_make_link,#tool_make_link_multi', fn: makeHyperlink, evt: 'click'}, {sel: '#tool_undo', fn: clickUndo, evt: 'click', key: ['Z', true]}, {sel: '#tool_redo', fn: clickRedo, evt: 'click', key: ['Y', true]}, {sel: '#tool_clone,#tool_clone_multi', fn: clickClone, evt: 'click', key: ['D', true]}, {sel: '#tool_group', fn: clickGroup, evt: 'click', key: ['G', true]}, {sel: '#tool_ungroup', fn: clickGroup, evt: 'click'}, {sel: '#tool_unlink_use', fn: clickGroup, evt: 'click'}, {sel: '[id^=tool_align]', fn: clickAlign, evt: 'click'}, // these two lines are required to make Opera work properly with the flyout mechanism // {sel: '#tools_rect_show', fn: clickRect, evt: 'click'}, // {sel: '#tools_ellipse_show', fn: clickEllipse, evt: 'click'}, {sel: '#tool_bold', fn: clickBold, evt: 'mousedown'}, {sel: '#tool_italic', fn: clickItalic, evt: 'mousedown'}, {sel: '#sidepanel_handle', fn: toggleSidePanel, key: ['X']}, {sel: '#copy_save_done', fn: cancelOverlays, evt: 'click'}, // Shortcuts not associated with buttons {key: 'ctrl+left', fn: function(){rotateSelected(0,1);}}, {key: 'ctrl+right', fn: function(){rotateSelected(1,1);}}, {key: 'ctrl+shift+left', fn: function(){rotateSelected(0,5);}}, {key: 'ctrl+shift+right', fn: function(){rotateSelected(1,5);}}, {key: 'shift+O', fn: selectPrev}, {key: 'shift+P', fn: selectNext}, {key: [modKey+'up', true], fn: function(){zoomImage(2);}}, {key: [modKey+'down', true], fn: function(){zoomImage(0.5);}}, {key: [modKey+']', true], fn: function(){moveUpDownSelected('Up');}}, {key: [modKey+'[', true], fn: function(){moveUpDownSelected('Down');}}, {key: ['up', true], fn: function(){moveSelected(0,-1);}}, {key: ['down', true], fn: function(){moveSelected(0,1);}}, {key: ['left', true], fn: function(){moveSelected(-1,0);}}, {key: ['right', true], fn: function(){moveSelected(1,0);}}, {key: 'shift+up', fn: function(){moveSelected(0,-10);}}, {key: 'shift+down', fn: function(){moveSelected(0,10);}}, {key: 'shift+left', fn: function(){moveSelected(-10,0);}}, {key: 'shift+right', fn: function(){moveSelected(10,0);}}, {key: ['alt+up', true], fn: function(){svgCanvas.cloneSelectedElements(0,-1);}}, {key: ['alt+down', true], fn: function(){svgCanvas.cloneSelectedElements(0,1);}}, {key: ['alt+left', true], fn: function(){svgCanvas.cloneSelectedElements(-1,0);}}, {key: ['alt+right', true], fn: function(){svgCanvas.cloneSelectedElements(1,0);}}, {key: ['alt+shift+up', true], fn: function(){svgCanvas.cloneSelectedElements(0,-10);}}, {key: ['alt+shift+down', true], fn: function(){svgCanvas.cloneSelectedElements(0,10);}}, {key: ['alt+shift+left', true], fn: function(){svgCanvas.cloneSelectedElements(-10,0);}}, {key: ['alt+shift+right', true], fn: function(){svgCanvas.cloneSelectedElements(10,0);}}, {key: 'A', fn: function(){svgCanvas.selectAllInCurrentLayer();}}, // Standard shortcuts {key: modKey+'z', fn: clickUndo}, {key: modKey + 'shift+z', fn: clickRedo}, {key: modKey + 'y', fn: clickRedo}, {key: modKey+'x', fn: cutSelected}, {key: modKey+'c', fn: copySelected}, {key: modKey+'v', fn: pasteInCenter} ]; // Tooltips not directly associated with a single function var key_assocs = { '4/Shift+4': '#tools_rect_show', '5/Shift+5': '#tools_ellipse_show' }; return { setAll: function() { var flyouts = {}; $.each(tool_buttons, function(i, opts) { // Bind function to button var btn; if (opts.sel) { btn = $(opts.sel); if (btn.length == 0) {return true;} // Skip if markup does not exist if (opts.evt) { if (svgedit.browser.isTouch() && opts.evt === 'click') { opts.evt = 'mousedown'; } btn[opts.evt](opts.fn); } // Add to parent flyout menu, if able to be displayed if (opts.parent && $(opts.parent + '_show').length != 0) { var f_h = $(opts.parent); if (!f_h.length) { f_h = makeFlyoutHolder(opts.parent.substr(1)); } f_h.append(btn); if (!$.isArray(flyouts[opts.parent])) { flyouts[opts.parent] = []; } flyouts[opts.parent].push(opts); } } // Bind function to shortcut key if (opts.key) { // Set shortcut based on options var keyval, shortcut = '', disInInp = true, fn = opts.fn, pd = false; if ($.isArray(opts.key)) { keyval = opts.key[0]; if (opts.key.length > 1) {pd = opts.key[1];} if (opts.key.length > 2) {disInInp = opts.key[2];} } else { keyval = opts.key; } keyval += ''; $.each(keyval.split('/'), function(i, key) { $(document).bind('keydown', key, function(e) { fn(); if (pd) { e.preventDefault(); } // Prevent default on ALL keys? return false; }); }); // Put shortcut in title if (opts.sel && !opts.hidekey && btn.attr('title')) { var newTitle = btn.attr('title').split('[')[0] + ' (' + keyval + ')'; key_assocs[keyval] = opts.sel; // Disregard for menu items if (!btn.parents('#main_menu').length) { btn.attr('title', newTitle); } } } }); // Setup flyouts setupFlyouts(flyouts); // Misc additional actions // Make 'return' keypress trigger the change event $('.attr_changer, #image_url').bind('keydown', 'return', function(evt) {$(this).change();evt.preventDefault();} ); $(window).bind('keydown', 'tab', function(e) { if (ui_context === 'canvas') { e.preventDefault(); selectNext(); } }).bind('keydown', 'shift+tab', function(e) { if (ui_context === 'canvas') { e.preventDefault(); selectPrev(); } }); $('#tool_zoom').dblclick(dblclickZoom); }, setTitles: function() { $.each(key_assocs, function(keyval, sel) { var menu = ($(sel).parents('#main_menu').length); $(sel).each(function() { var t; if (menu) { t = $(this).text().split(' [')[0]; } else { t = this.title.split(' [')[0]; } var key_str = ''; // Shift+Up $.each(keyval.split('/'), function(i, key) { var mod_bits = key.split('+'), mod = ''; if (mod_bits.length > 1) { mod = mod_bits[0] + '+'; key = mod_bits[1]; } key_str += (i?'/':'') + mod + (uiStrings['key_'+key] || key); }); if (menu) { this.lastChild.textContent = t +' ['+key_str+']'; } else { this.title = t +' ['+key_str+']'; } }); }); }, getButtonData: function(sel) { var b; $.each(tool_buttons, function(i, btn) { if (btn.sel === sel) {b = btn;} }); return b; } }; }()); Actions.setAll(); // Select given tool Editor.ready(function() { var tool, itool = curConfig.initTool, container = $('#tools_left, #svg_editor .tools_flyout'), pre_tool = container.find('#tool_' + itool), reg_tool = container.find('#' + itool); if (pre_tool.length) { tool = pre_tool; } else if (reg_tool.length) { tool = reg_tool; } else { tool = $('#tool_select'); } tool.click().mouseup(); if (curConfig.wireframe) { $('#tool_wireframe').click(); } if (curConfig.showlayers) { toggleSidePanel(); } $('#rulers').toggle(!!curConfig.showRulers); if (curConfig.showRulers) { $('#show_rulers')[0].checked = true; } if (curConfig.baseUnit) { $('#base_unit').val(curConfig.baseUnit); } if (curConfig.gridSnapping) { $('#grid_snapping_on')[0].checked = true; } if (curConfig.snappingStep) { $('#grid_snapping_step').val(curConfig.snappingStep); } if (curConfig.gridColor) { $('#grid_color').val(curConfig.gridColor); } }); // init SpinButtons $('#rect_rx').SpinButton({ min: 0, max: 1000, callback: changeRectRadius }); $('#stroke_width').SpinButton({ min: 0, max: 99, smallStep: 0.1, callback: changeStrokeWidth }); $('#angle').SpinButton({ min: -180, max: 180, step: 5, callback: changeRotationAngle }); $('#font_size').SpinButton({ min: 0.001, stepfunc: stepFontSize, callback: changeFontSize }); $('#group_opacity').SpinButton({ min: 0, max: 100, step: 5, callback: changeOpacity }); $('#blur').SpinButton({ min: 0, max: 10, step: 0.1, callback: changeBlur }); $('#zoom').SpinButton({ min: 0.001, max: 10000, step: 50, stepfunc: stepZoom, callback: changeZoom }) // Set default zoom .val(svgCanvas.getZoom() * 100); $('#workarea').contextMenu({ menu: 'cmenu_canvas', inSpeed: 0 }, function(action, el, pos) { switch (action) { case 'delete': deleteSelected(); break; case 'cut': cutSelected(); break; case 'copy': copySelected(); break; case 'paste': svgCanvas.pasteElements(); break; case 'paste_in_place': svgCanvas.pasteElements('in_place'); break; case 'group': svgCanvas.groupSelectedElements(); break; case 'ungroup': svgCanvas.ungroupSelectedElement(); break; case 'move_front': moveToTopSelected(); break; case 'move_up': moveUpDownSelected('Up'); break; case 'move_down': moveUpDownSelected('Down'); break; case 'move_back': moveToBottomSelected(); break; default: if (svgedit.contextmenu && svgedit.contextmenu.hasCustomHandler(action)) { svgedit.contextmenu.getCustomHandler(action).call(); } break; } if (svgCanvas.clipBoard.length) { canv_menu.enableContextMenuItems('#paste,#paste_in_place'); } } ); var lmenu_func = function(action, el, pos) { switch ( action ) { case 'dupe': cloneLayer(); break; case 'delete': deleteLayer(); break; case 'merge_down': mergeLayer(); break; case 'merge_all': svgCanvas.mergeAllLayers(); updateContextPanel(); populateLayers(); break; } }; $('#layerlist').contextMenu({ menu: 'cmenu_layers', inSpeed: 0 }, lmenu_func ); $('#layer_moreopts').contextMenu({ menu: 'cmenu_layers', inSpeed: 0, allowLeft: true }, lmenu_func ); $('.contextMenu li').mousedown(function(ev) { ev.preventDefault(); }); $('#cmenu_canvas li').disableContextMenu(); canv_menu.enableContextMenuItems('#delete,#cut,#copy'); window.addEventListener('beforeunload', function(e) { if (window.localStorage) { var name = 'svgedit-' + Editor.curConfig.canvasName; window.localStorage.setItem(name, svgCanvas.getSvgString()); Editor.showSaveWarning = false; } // Suppress warning if page is empty if (undoMgr.getUndoStackSize() === 0) { Editor.showSaveWarning = false; } // showSaveWarning is set to 'false' when the page is saved. if (!curConfig.no_save_warning && Editor.showSaveWarning) { // Browser already asks question about closing the page e.returnValue = uiStrings.notification.unsavedChanges; // Firefox needs this when beforeunload set by addEventListener (even though message is not used) return uiStrings.notification.unsavedChanges; } }, false); Editor.openPrep = function(func) { $('#main_menu').hide(); if (undoMgr.getUndoStackSize() === 0) { func(true); } else { $.confirm(uiStrings.notification.QwantToOpen, func); } }; function onDragEnter(e) { e.stopPropagation(); e.preventDefault(); // and indicator should be displayed here, such as "drop files here" } function onDragOver(e) { e.stopPropagation(); e.preventDefault(); } function onDragLeave(e) { e.stopPropagation(); e.preventDefault(); // hypothetical indicator should be removed here } // Use HTML5 File API: http://www.w3.org/TR/FileAPI/ // if browser has HTML5 File API support, then we will show the open menu item // and provide a file input to click. When that change event fires, it will // get the text contents of the file and send it to the canvas if (window.FileReader) { var importImage = function(e) { $.process_cancel(uiStrings.notification.loadingImage); e.stopPropagation(); e.preventDefault(); $('#workarea').removeAttr('style'); $('#main_menu').hide(); var file = (e.type == 'drop') ? e.dataTransfer.files[0] : this.files[0]; if (!file) { return; } if (file.type.indexOf('image') != -1) { // Detected an image // svg handling var reader; if (file.type.indexOf('svg') != -1) { reader = new FileReader(); reader.onloadend = function(e) { svgCanvas.importSvgString(e.target.result, true); svgCanvas.ungroupSelectedElement(); svgCanvas.ungroupSelectedElement(); svgCanvas.groupSelectedElements(); svgCanvas.alignSelectedElements('m', 'page'); svgCanvas.alignSelectedElements('c', 'page'); }; reader.readAsText(file); } else { //bitmap handling reader = new FileReader(); reader.onloadend = function(e) { // let's insert the new image until we know its dimensions var insertNewImage = function(width, height) { var newImage = svgCanvas.addSvgElementFromJson({ element: 'image', attr: { x: 0, y: 0, width: width, height: height, id: svgCanvas.getNextId(), style: 'pointer-events:inherit' } }); svgCanvas.setHref(newImage, e.target.result); svgCanvas.selectOnly([newImage]); svgCanvas.alignSelectedElements('m', 'page'); svgCanvas.alignSelectedElements('c', 'page'); updateContextPanel(); }; // create dummy img so we know the default dimensions var imgWidth = 100; var imgHeight = 100; var img = new Image(); img.src = e.target.result; img.style.opacity = 0; img.onload = function() { imgWidth = img.offsetWidth; imgHeight = img.offsetHeight; insertNewImage(imgWidth, imgHeight); }; }; reader.readAsDataURL(file); } } }; workarea[0].addEventListener('dragenter', onDragEnter, false); workarea[0].addEventListener('dragover', onDragOver, false); workarea[0].addEventListener('dragleave', onDragLeave, false); workarea[0].addEventListener('drop', importImage, false); var open = $('<input type="file">').change(function() { var f = this; Editor.openPrep(function(ok) { if (!ok) {return;} svgCanvas.clear(); if (f.files.length==1) { $.process_cancel(uiStrings.notification.loadingImage); var reader = new FileReader(); reader.onloadend = function(e) { loadSvgString(e.target.result); updateCanvas(); }; reader.readAsText(f.files[0]); } }); }); $('#tool_open').show().prepend(open); var imgImport = $('<input type="file">').change(importImage); $('#tool_import').show().prepend(imgImport); } // $(function() { updateCanvas(true); // }); // var revnums = "svg-editor.js ($Rev$) "; // revnums += svgCanvas.getVersion(); // $('#copyright')[0].setAttribute('title', revnums); // Callback handler for embedapi.js try { window.addEventListener('message', function(e) { // We accept and post strings for the sake of IE9 support if (typeof e.data !== 'string' || e.data.charAt() === '|') { return; } var data = JSON.parse(e.data); if (!data || typeof data !== 'object' || data.namespace !== 'svgCanvas') { return; } var cbid = data.id, name = data.name, args = data.args; try { e.source.postMessage(JSON.stringify({namespace: 'svg-edit', id: cbid, result: svgCanvas[name].apply(svgCanvas, args)}), '*'); } catch(err) { e.source.postMessage(JSON.stringify({namespace: 'svg-edit', id: cbid, error: err.message}), '*'); } }, false); } catch(err) { window.embed_error = err; } // For Compatibility with older extensions $(function() { window.svgCanvas = svgCanvas; svgCanvas.ready = svgEditor.ready; }); Editor.setLang = function(lang, allStrings) { $.pref('lang', lang); $('#lang_select').val(lang); if (!allStrings) { return; } var notif = allStrings.notification; // $.extend will only replace the given strings var oldLayerName = $('#layerlist tr.layersel td.layername').text(); var rename_layer = (oldLayerName == uiStrings.common.layer + ' 1'); $.extend(uiStrings, allStrings); svgCanvas.setUiStrings(allStrings); Actions.setTitles(); if (rename_layer) { svgCanvas.renameCurrentLayer(uiStrings.common.layer + ' 1'); populateLayers(); } svgCanvas.runExtensions('langChanged', lang); // Update flyout tooltips setFlyoutTitles(); // Copy title for certain tool elements var elems = { '#stroke_color': '#tool_stroke .icon_label, #tool_stroke .color_block', '#fill_color': '#tool_fill label, #tool_fill .color_block', '#linejoin_miter': '#cur_linejoin', '#linecap_butt': '#cur_linecap' }; $.each(elems, function(source, dest) { $(dest).attr('title', $(source)[0].title); }); // Copy alignment titles $('#multiselected_panel div[id^=tool_align]').each(function() { $('#tool_pos' + this.id.substr(10))[0].title = this.title; }); }; }; var callbacks = []; Editor.ready = function(cb) { if (!isReady) { callbacks.push(cb); } else { cb(); } }; Editor.runCallbacks = function() { $.each(callbacks, function() { this(); }); isReady = true; }; Editor.loadFromString = function(str) { Editor.ready(function() { loadSvgString(str); }); }; Editor.disableUI = function(featList) { // $(function() { // $('#tool_wireframe, #tool_image, #main_button, #tool_source, #sidepanels').remove(); // $('#tools_top').css('left', 5); // }); }; Editor.loadFromURL = function(url, opts) { if (!opts) {opts = {};} var cache = opts.cache; var cb = opts.callback; Editor.ready(function() { $.ajax({ 'url': url, 'dataType': 'text', cache: !!cache, beforeSend:function(){ $.process_cancel(uiStrings.notification.loadingImage); }, success: function(str) { loadSvgString(str, cb); }, error: function(xhr, stat, err) { if (xhr.status != 404 && xhr.responseText) { loadSvgString(xhr.responseText, cb); } else { $.alert(uiStrings.notification.URLloadFail + ': \n' + err, cb); } }, complete:function(){ $('#dialog_box').hide(); } }); }); }; Editor.loadFromDataURI = function(str) { Editor.ready(function() { var pre = 'data:image/svg+xml;base64,'; var src = str.substring(pre.length); loadSvgString(svgedit.utilities.decode64(src)); }); }; Editor.addExtension = function() { var args = arguments; // Note that we don't want this on Editor.ready since some extensions // may want to run before then (like server_opensave). $(function() { if (svgCanvas) {svgCanvas.addExtension.apply(this, args);} }); }; return Editor; }(jQuery)); // Run init once DOM is loaded $(svgEditor.init); }());