From d3e3a23d91513a600c5350b558c272b7085cd261 Mon Sep 17 00:00:00 2001 From: cubells Date: Tue, 11 Jul 2017 06:51:33 +0200 Subject: [PATCH 1/7] [ADD] web_widget_digitized_signature --- web_widget_digitized_signature/README.rst | 70 + web_widget_digitized_signature/__init__.py | 6 + web_widget_digitized_signature/__openerp__.py | 29 + web_widget_digitized_signature/i18n/es.po | 79 + .../models/__init__.py | 7 + .../models/mail_thread.py | 40 + .../models/res_users.py | 16 + .../static/description/icon.png | Bin 0 -> 9117 bytes .../static/lib/jSignature/jSignatureCustom.js | 1392 +++++++++++++++++ .../static/src/js/digital_sign.js | 122 ++ .../static/src/xml/digital_sign.xml | 24 + .../views/res_users_view.xml | 27 + .../views/web_digital_sign_view.xml | 11 + 13 files changed, 1823 insertions(+) create mode 100644 web_widget_digitized_signature/README.rst create mode 100644 web_widget_digitized_signature/__init__.py create mode 100644 web_widget_digitized_signature/__openerp__.py create mode 100644 web_widget_digitized_signature/i18n/es.po create mode 100644 web_widget_digitized_signature/models/__init__.py create mode 100644 web_widget_digitized_signature/models/mail_thread.py create mode 100644 web_widget_digitized_signature/models/res_users.py create mode 100644 web_widget_digitized_signature/static/description/icon.png create mode 100644 web_widget_digitized_signature/static/lib/jSignature/jSignatureCustom.js create mode 100644 web_widget_digitized_signature/static/src/js/digital_sign.js create mode 100644 web_widget_digitized_signature/static/src/xml/digital_sign.xml create mode 100644 web_widget_digitized_signature/views/res_users_view.xml create mode 100644 web_widget_digitized_signature/views/web_digital_sign_view.xml diff --git a/web_widget_digitized_signature/README.rst b/web_widget_digitized_signature/README.rst new file mode 100644 index 00000000..53c47a52 --- /dev/null +++ b/web_widget_digitized_signature/README.rst @@ -0,0 +1,70 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +======================= +Web Digitized Signature +======================= + +This module provides a widget for binary fields that allows to digitize a +signature and store it as an image. + +As demonstration, it includes this widget at user level, so that we can store +a signature image for each user. + +Configuration +============= + +#. To use this module, you need to add ``widget="signature"`` to your binary + field in your view. +#. You can specifify signature dimensions like the following: + ```` + +Usage +===== + +#. Go to *Settings > Users > Users*. +#. Open one of the existing users. +#. You can set a digital signature for it on the field "Signature". + + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/162/9.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Jay Vora +* Vicent Cubells + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/web_widget_digitized_signature/__init__.py b/web_widget_digitized_signature/__init__.py new file mode 100644 index 00000000..96b2d789 --- /dev/null +++ b/web_widget_digitized_signature/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Copyright 2004-2010 OpenERP SA () +# Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/web_widget_digitized_signature/__openerp__.py b/web_widget_digitized_signature/__openerp__.py new file mode 100644 index 00000000..1f61492d --- /dev/null +++ b/web_widget_digitized_signature/__openerp__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright 2004-2010 OpenERP SA () +# Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. +# Copyright 2017 Tecnativa - Vicent Cubells +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Web Widget Digitized Signature", + "version": "9.0.1.0.0", + "author": "Serpent Consulting Services Pvt. Ltd., " + "Agile Business Group, " + "Tecnativa, " + "Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": 'Web', + 'depends': [ + 'web', + 'mail', + ], + 'data': [ + 'views/web_digital_sign_view.xml', + 'views/res_users_view.xml', + ], + 'website': 'http://www.serpentcs.com', + 'qweb': [ + 'static/src/xml/digital_sign.xml', + ], + 'installable': True, +} diff --git a/web_widget_digitized_signature/i18n/es.po b/web_widget_digitized_signature/i18n/es.po new file mode 100644 index 00000000..0f1120bf --- /dev/null +++ b/web_widget_digitized_signature/i18n/es.po @@ -0,0 +1,79 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0c\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-11 04:42+0000\n" +"PO-Revision-Date: 2017-07-11 04:42+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "Limpiar" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "No se puede mostrar la imagen seleccionada." + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:26 +#, python-format +msgid "Deletion date: %s" +msgstr "Fecha de eliminación: %s" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "Dibuje su firma" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "Imagen" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "Firma" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:23 +#, python-format +msgid "Signature date: %s" +msgstr "Fecha de la firma: %s" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:21 +#, python-format +msgid "Signature has been created." +msgstr "La firma se ha creado." + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:25 +#, python-format +msgid "Signature has been deleted." +msgstr "La firma se ha eliminado." + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "Usuarios" + diff --git a/web_widget_digitized_signature/models/__init__.py b/web_widget_digitized_signature/models/__init__.py new file mode 100644 index 00000000..3f2b1438 --- /dev/null +++ b/web_widget_digitized_signature/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright 2004-2010 OpenERP SA () +# Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import mail_thread +from . import res_users diff --git a/web_widget_digitized_signature/models/mail_thread.py b/web_widget_digitized_signature/models/mail_thread.py new file mode 100644 index 00000000..63b84251 --- /dev/null +++ b/web_widget_digitized_signature/models/mail_thread.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Copyright 2004-2010 OpenERP SA () +# Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. +# Copyright 2017 Tecnativa - Vicent Cubells +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import base64 +from openerp import _, models, fields + + +class MailThread(models.Model): + _inherit = "mail.thread" + + def _track_signature(self, values, field): + """ This method allows to track creation and deletion of signature + field. You must call this method in order to display a message + in the chatter with information of the changes in the signature. + + :param values: a dict with the values being written + :param field: name of the field that must be tracked + """ + if field in values: + attachments = [] + messages = [] + if values.get(field): + content = base64.b64decode(values.get(field)) + attachments = [('signature', content)] + messages.append(_('Signature has been created.')) + messages.append( + _('Signature date: %s' % fields.Datetime.now())) + else: + messages.append(_('Signature has been deleted.')) + messages.append(_('Deletion date: %s' % fields.Datetime.now())) + msg_body = '
    ' + for message in messages: + msg_body += '
  • ' + msg_body += message + msg_body += '
  • ' + msg_body += '
' + self.message_post(body=msg_body, attachments=attachments) diff --git a/web_widget_digitized_signature/models/res_users.py b/web_widget_digitized_signature/models/res_users.py new file mode 100644 index 00000000..c1275b66 --- /dev/null +++ b/web_widget_digitized_signature/models/res_users.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2004-2010 OpenERP SA () +# Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. +# Copyright 2017 Tecnativa - Vicent Cubells +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp import models, fields + + +class Users(models.Model): + _name = 'res.users' + _inherit = 'res.users' + + signature_image = fields.Binary( + string='Signature', + ) diff --git a/web_widget_digitized_signature/static/description/icon.png b/web_widget_digitized_signature/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a77055280de4198812ad73dfa9a99235f2c55a6e GIT binary patch literal 9117 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 + +*/ +;(function() { + +var apinamespace = 'jSignature' + +/** +Allows one to delay certain eventual action by setting up a timer for it and allowing one to delay it +by "kick"ing it. Sorta like "kick the can down the road" + +@public +@class +@param +@returns {Type} +*/ +var KickTimerClass = function(time, callback) { + var timer + this.kick = function() { + clearTimeout(timer) + timer = setTimeout( + callback + , time + ) + } + this.clear = function() { + clearTimeout(timer) + } + return this +} + +var PubSubClass = function(context){ + 'use strict' + /* @preserve + ----------------------------------------------------------------------------------------------- + JavaScript PubSub library + 2012 (c) Willow Systems Corp (www.willow-systems.com) + based on Peter Higgins (dante@dojotoolkit.org) + Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly. + Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see: + http://dojofoundation.org/license for more information. + ----------------------------------------------------------------------------------------------- + */ + this.topics = {} + // here we choose what will be "this" for the called events. + // if context is defined, it's context. Else, 'this' is this instance of PubSub + this.context = context ? context : this + /** + * Allows caller to emit an event and pass arguments to event listeners. + * @public + * @function + * @param topic {String} Name of the channel on which to voice this event + * @param **arguments Any number of arguments you want to pass to the listeners of this event. + */ + this.publish = function(topic, arg1, arg2, etc) { + 'use strict' + if (this.topics[topic]) { + var currentTopic = this.topics[topic] + , args = Array.prototype.slice.call(arguments, 1) + , toremove = [] + , fn + , i, l + , pair + + for (i = 0, l = currentTopic.length; i < l; i++) { + pair = currentTopic[i] // this is a [function, once_flag] array + fn = pair[0] + if (pair[1] /* 'run once' flag set */){ + pair[0] = function(){} + toremove.push(i) + } + fn.apply(this.context, args) + } + for (i = 0, l = toremove.length; i < l; i++) { + currentTopic.splice(toremove[i], 1) + } + } + } + /** + * Allows listener code to subscribe to channel and be called when data is available + * @public + * @function + * @param topic {String} Name of the channel on which to voice this event + * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel. + * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once. + * @returns {Object} A token object that cen be used for unsubscribing. + */ + this.subscribe = function(topic, callback, once) { + 'use strict' + if (!this.topics[topic]) { + this.topics[topic] = [[callback, once]]; + } else { + this.topics[topic].push([callback,once]); + } + return { + "topic": topic, + "callback": callback + }; + }; + /** + * Allows listener code to unsubscribe from a channel + * @public + * @function + * @param token {Object} A token object that was returned by `subscribe` method + */ + this.unsubscribe = function(token) { + if (this.topics[token.topic]) { + var currentTopic = this.topics[token.topic] + + for (var i = 0, l = currentTopic.length; i < l; i++) { + if (currentTopic[i][0] === token.callback) { + currentTopic.splice(i, 1) + } + } + } + } +} + +/// Returns front, back and "decor" colors derived from element (as jQuery obj) +function getColors($e){ + var tmp + , undef + , frontcolor = $e.css('color') + , backcolor + , e = $e[0] + + var toOfDOM = false + while(e && !backcolor && !toOfDOM){ + try{ + tmp = $(e).css('background-color') + } catch (ex) { + tmp = 'transparent' + } + if (tmp !== 'transparent' && tmp !== 'rgba(0, 0, 0, 0)'){ + backcolor = tmp + } + toOfDOM = e.body + e = e.parentNode + } + + var rgbaregex = /rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/ // modern browsers + , hexregex = /#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/ // IE 8 and less. + , frontcolorcomponents + + // Decomposing Front color into R, G, B ints + tmp = undef + tmp = frontcolor.match(rgbaregex) + if (tmp){ + frontcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)} + } else { + tmp = frontcolor.match(hexregex) + if (tmp) { + frontcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)} + } + } +// if(!frontcolorcomponents){ +// frontcolorcomponents = {'r':255,'g':255,'b':255} +// } + + var backcolorcomponents + // Decomposing back color into R, G, B ints + if(!backcolor){ + // HIghly unlikely since this means that no background styling was applied to any element from here to top of dom. + // we'll pick up back color from front color + if(frontcolorcomponents){ + if (Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) > 127){ + backcolorcomponents = {'r':0,'g':0,'b':0} + } else { + backcolorcomponents = {'r':255,'g':255,'b':255} + } + } else { + // arg!!! front color is in format we don't understand (hsl, named colors) + // Let's just go with white background. + backcolorcomponents = {'r':255,'g':255,'b':255} + } + } else { + tmp = undef + tmp = backcolor.match(rgbaregex) + if (tmp){ + backcolorcomponents = {'r':parseInt(tmp[1],10),'g':parseInt(tmp[2],10),'b':parseInt(tmp[3],10)} + } else { + tmp = backcolor.match(hexregex) + if (tmp) { + backcolorcomponents = {'r':parseInt(tmp[1],16),'g':parseInt(tmp[2],16),'b':parseInt(tmp[3],16)} + } + } +// if(!backcolorcomponents){ +// backcolorcomponents = {'r':0,'g':0,'b':0} +// } + } + + // Deriving Decor color + // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill. + + var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'} + , decorcolorcomponents + , frontcolorbrightness + , adjusted + + if (frontcolorcomponents && backcolorcomponents){ + var backcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) + + frontcolorbrightness = Math.max.apply(null, [backcolorcomponents.r, backcolorcomponents.g, backcolorcomponents.b]) + adjusted = Math.round(frontcolorbrightness + (-1 * (frontcolorbrightness - backcolorbrightness) * 0.75)) // "dimming" the difference between pen and back. + decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted} // always shade of gray + } else if (frontcolorcomponents) { + frontcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) + var polarity = +1 + if (frontcolorbrightness > 127){ + polarity = -1 + } + // shifting by 25% (64 points on RGB scale) + adjusted = Math.round(frontcolorbrightness + (polarity * 96)) // "dimming" the pen's color by 75% to get decor color. + decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted} // always shade of gray + } else { + decorcolorcomponents = {'r':191,'g':191,'b':191} // always shade of gray + } + + return { + 'color': frontcolor + , 'background-color': backcolorcomponents? toRGBfn(backcolorcomponents) : backcolor + , 'decor-color': toRGBfn(decorcolorcomponents) + } +} + +function Vector(x,y){ + this.x = x + this.y = y + this.reverse = function(){ + return new this.constructor( + this.x * -1 + , this.y * -1 + ) + } + this._length = null + this.getLength = function(){ + if (!this._length){ + this._length = Math.sqrt( Math.pow(this.x, 2) + Math.pow(this.y, 2) ) + } + return this._length + } + + var polarity = function (e){ + return Math.round(e / Math.abs(e)) + } + this.resizeTo = function(length){ + // proportionally changes x,y such that the hypotenuse (vector length) is = new length + if (this.x === 0 && this.y === 0){ + this._length = 0 + } else if (this.x === 0){ + this._length = length + this.y = length * polarity(this.y) + } else if(this.y === 0){ + this._length = length + this.x = length * polarity(this.x) + } else { + var proportion = Math.abs(this.y / this.x) + , x = Math.sqrt(Math.pow(length, 2) / (1 + Math.pow(proportion, 2))) + , y = proportion * x + this._length = length + this.x = x * polarity(this.x) + this.y = y * polarity(this.y) + } + return this + } + + /** + * Calculates the angle between 'this' vector and another. + * @public + * @function + * @returns {Number} The angle between the two vectors as measured in PI. + */ + this.angleTo = function(vectorB) { + var divisor = this.getLength() * vectorB.getLength() + if (divisor === 0) { + return 0 + } else { + // JavaScript floating point math is screwed up. + // because of it, the core of the formula can, on occasion, have values + // over 1.0 and below -1.0. + return Math.acos( + Math.min( + Math.max( + ( this.x * vectorB.x + this.y * vectorB.y ) / divisor + , -1.0 + ) + , 1.0 + ) + ) / Math.PI + } + } +} + +function Point(x,y){ + this.x = x + this.y = y + + this.getVectorToCoordinates = function (x, y) { + return new Vector(x - this.x, y - this.y) + } + this.getVectorFromCoordinates = function (x, y) { + return this.getVectorToCoordinates(x, y).reverse() + } + this.getVectorToPoint = function (point) { + return new Vector(point.x - this.x, point.y - this.y) + } + this.getVectorFromPoint = function (point) { + return this.getVectorToPoint(point).reverse() + } +} + +/* + * About data structure: + * We don't store / deal with "pictures" this signature capture code captures "vectors" + * + * We don't store bitmaps. We store "strokes" as arrays of arrays. (Actually, arrays of objects containing arrays of coordinates. + * + * Stroke = mousedown + mousemoved * n (+ mouseup but we don't record that as that was the "end / lack of movement" indicator) + * + * Vectors = not classical vectors where numbers indicated shift relative last position. Our vectors are actually coordinates against top left of canvas. + * we could calc the classical vectors, but keeping the the actual coordinates allows us (through Math.max / min) + * to calc the size of resulting drawing very quickly. If we want classical vectors later, we can always get them in backend code. + * + * So, the data structure: + * + * var data = [ + * { // stroke starts + * x : [101, 98, 57, 43] // x points + * , y : [1, 23, 65, 87] // y points + * } // stroke ends + * , { // stroke starts + * x : [55, 56, 57, 58] // x points + * , y : [101, 97, 54, 4] // y points + * } // stroke ends + * , { // stroke consisting of just a dot + * x : [53] // x points + * , y : [151] // y points + * } // stroke ends + * ] + * + * we don't care or store stroke width (it's canvas-size-relative), color, shadow values. These can be added / changed on whim post-capture. + * + */ +function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStrokeFn){ + this.data = storageObject // we expect this to be an instance of Array + this.context = context + + if (storageObject.length){ + // we have data to render + var numofstrokes = storageObject.length + , stroke + , numofpoints + + for (var i = 0; i < numofstrokes; i++){ + stroke = storageObject[i] + numofpoints = stroke.x.length + startStrokeFn.call(context, stroke) + for(var j = 1; j < numofpoints; j++){ + addToStrokeFn.call(context, stroke, j) + } + endStrokeFn.call(context, stroke) + } + } + + this.changed = function(){} + + this.startStrokeFn = startStrokeFn + this.addToStrokeFn = addToStrokeFn + this.endStrokeFn = endStrokeFn + + this.inStroke = false + + this._lastPoint = null + this._stroke = null + this.startStroke = function(point){ + if(point && typeof(point.x) == "number" && typeof(point.y) == "number"){ + this._stroke = {'x':[point.x], 'y':[point.y]} + this.data.push(this._stroke) + this._lastPoint = point + this.inStroke = true + // 'this' does not work same inside setTimeout( + var stroke = this._stroke + , fn = this.startStrokeFn + , context = this.context + setTimeout( + // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. + function() {fn.call(context, stroke)} + , 3 + ) + return point + } else { + return null + } + } + // that "5" at the very end of this if is important to explain. + // we do NOT render links between two captured points (in the middle of the stroke) if the distance is shorter than that number. + // not only do we NOT render it, we also do NOT capture (add) these intermediate points to storage. + // when clustering of these is too tight, it produces noise on the line, which, because of smoothing, makes lines too curvy. + // maybe, later, we can expose this as a configurable setting of some sort. + this.addToStroke = function(point){ + if (this.inStroke && + typeof(point.x) === "number" && + typeof(point.y) === "number" && + // calculates absolute shift in diagonal pixels away from original point + (Math.abs(point.x - this._lastPoint.x) + Math.abs(point.y - this._lastPoint.y)) > 4 + ){ + var positionInStroke = this._stroke.x.length + this._stroke.x.push(point.x) + this._stroke.y.push(point.y) + this._lastPoint = point + + var stroke = this._stroke + , fn = this.addToStrokeFn + , context = this.context + setTimeout( + // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. + function() {fn.call(context, stroke, positionInStroke)} + , 3 + ) + return point + } else { + return null + } + } + this.endStroke = function(){ + var c = this.inStroke + this.inStroke = false + this._lastPoint = null + if (c){ + var stroke = this._stroke + , fn = this.endStrokeFn // 'this' does not work same inside setTimeout( + , context = this.context + , changedfn = this.changed + setTimeout( + // some IE's don't support passing args per setTimeout API. Have to create closure every time instead. + function(){ + fn.call(context, stroke) + changedfn.call(context) + } + , 3 + ) + return true + } else { + return null + } + } +} + +var basicDot = function(ctx, x, y, size){ + var fillStyle = ctx.fillStyle + ctx.fillStyle = ctx.strokeStyle + ctx.fillRect(x + size / -2 , y + size / -2, size, size) + ctx.fillStyle = fillStyle +} +, basicLine = function(ctx, startx, starty, endx, endy){ + ctx.beginPath() + ctx.moveTo(startx, starty) + ctx.lineTo(endx, endy) + ctx.stroke() +} +, basicCurve = function(ctx, startx, starty, endx, endy, cp1x, cp1y, cp2x, cp2y){ + ctx.beginPath() + ctx.moveTo(startx, starty) + ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endx, endy) + ctx.stroke() +} +, strokeStartCallback = function(stroke) { + // this = jSignatureClass instance + basicDot(this.canvasContext, stroke.x[0], stroke.y[0], this.settings.lineWidth) +} +, strokeAddCallback = function(stroke, positionInStroke){ + // this = jSignatureClass instance + + // Because we are funky this way, here we draw TWO curves. + // 1. POSSIBLY "this line" - spanning from point right before us, to this latest point. + // 2. POSSIBLY "prior curve" - spanning from "latest point" to the one before it. + + // Why you ask? + // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke. + // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck! + // We want to approximate pretty curves in-place of those ugly lines. + // To approximate a very nice curve we need to know the direction of line before and after. + // Hence, on long lines we actually wait for another point beyond it to come back from + // mousemoved before we draw this curve. + + // So for "prior curve" to be calc'ed we need 4 points + // A, B, C, D (we are on D now, A is 3 points in the past.) + // and 3 lines: + // pre-line (from points A to B), + // this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.) + // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet) + // + // Well, actually, we don't need to *know* the point A, just the vector A->B + var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) + , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) + , CDvector = Cpoint.getVectorToPoint(Dpoint) + + // Again, we have a chance here to draw TWO things: + // BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and + // CD Line (only if it's short) + + // So, let's start with BC curve. + // if there is only 2 points in stroke array, we don't have "history" long enough to have point B, let alone point A. + // Falling through to drawing line CD is proper, as that's the only line we have points for. + if(positionInStroke > 1) { + // we are here when there are at least 3 points in stroke array. + var Bpoint = new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2]) + , BCvector = Bpoint.getVectorToPoint(Cpoint) + , ABvector + if(BCvector.getLength() > this.lineCurveThreshold){ + // Yey! Pretty curves, here we come! + if(positionInStroke > 2) { + // we are here when at least 4 points in stroke array. + ABvector = (new Point(stroke.x[positionInStroke-3], stroke.y[positionInStroke-3])).getVectorToPoint(Bpoint) + } else { + ABvector = new Vector(0,0) + } + + var minlenfraction = 0.05 + , maxlen = BCvector.getLength() * 0.35 + , ABCangle = BCvector.angleTo(ABvector.reverse()) + , BCDangle = CDvector.angleTo(BCvector.reverse()) + , BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo( + Math.max(minlenfraction, ABCangle) * maxlen + ) + , CCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo( + Math.max(minlenfraction, BCDangle) * maxlen + ) + + basicCurve( + this.canvasContext + , Bpoint.x + , Bpoint.y + , Cpoint.x + , Cpoint.y + , Bpoint.x + BCP1vector.x + , Bpoint.y + BCP1vector.y + , Cpoint.x + CCP2vector.x + , Cpoint.y + CCP2vector.y + ) + } + } + if(CDvector.getLength() <= this.lineCurveThreshold){ + basicLine( + this.canvasContext + , Cpoint.x + , Cpoint.y + , Dpoint.x + , Dpoint.y + ) + } +} +, strokeEndCallback = function(stroke){ + // this = jSignatureClass instance + + // Here we tidy up things left unfinished in last strokeAddCallback run. + + // What's POTENTIALLY left unfinished there is the curve between the last points + // in the stroke, if the len of that line is more than lineCurveThreshold + // If the last line was shorter than lineCurveThreshold, it was drawn there, and there + // is nothing for us here to do. + // We can also be called when there is only one point in the stroke (meaning, the + // stroke was just a dot), in which case, again, there is nothing for us to do. + + // So for "this curve" to be calc'ed we need 3 points + // A, B, C + // and 2 lines: + // pre-line (from points A to B), + // this line (from points B to C) + // Well, actually, we don't need to *know* the point A, just the vector A->B + // so, we really need points B, C and AB vector. + var positionInStroke = stroke.x.length - 1 + + if (positionInStroke > 0){ + // there are at least 2 points in the stroke.we are in business. + var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) + , Bpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) + , BCvector = Bpoint.getVectorToPoint(Cpoint) + , ABvector + if (BCvector.getLength() > this.lineCurveThreshold){ + // yep. This one was left undrawn in prior callback. Have to draw it now. + if (positionInStroke > 1){ + // we have at least 3 elems in stroke + ABvector = (new Point(stroke.x[positionInStroke-2], stroke.y[positionInStroke-2])).getVectorToPoint(Bpoint) + var BCP1vector = new Vector(ABvector.x + BCvector.x, ABvector.y + BCvector.y).resizeTo(BCvector.getLength() / 2) + basicCurve( + this.canvasContext + , Bpoint.x + , Bpoint.y + , Cpoint.x + , Cpoint.y + , Bpoint.x + BCP1vector.x + , Bpoint.y + BCP1vector.y + , Cpoint.x + , Cpoint.y + ) + } else { + // Since there is no AB leg, there is no curve to draw. This line is still "long" but no curve. + basicLine( + this.canvasContext + , Bpoint.x + , Bpoint.y + , Cpoint.x + , Cpoint.y + ) + } + } + } +} + + +/* +var getDataStats = function(){ + var strokecnt = strokes.length + , stroke + , pointid + , pointcnt + , x, y + , maxX = Number.NEGATIVE_INFINITY + , maxY = Number.NEGATIVE_INFINITY + , minX = Number.POSITIVE_INFINITY + , minY = Number.POSITIVE_INFINITY + for(strokeid = 0; strokeid < strokecnt; strokeid++){ + stroke = strokes[strokeid] + pointcnt = stroke.length + for(pointid = 0; pointid < pointcnt; pointid++){ + x = stroke.x[pointid] + y = stroke.y[pointid] + if (x > maxX){ + maxX = x + } else if (x < minX) { + minX = x + } + if (y > maxY){ + maxY = y + } else if (y < minY) { + minY = y + } + } + } + return {'maxX': maxX, 'minX': minX, 'maxY': maxY, 'minY': minY} +} +*/ + +function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, settingsWidth, apinamespace, globalEvents){ + 'use strict' + if ( settingsWidth === 'ratio' || settingsWidth.split('')[settingsWidth.length - 1] === '%' ) { + + this.eventTokens[apinamespace + '.parentresized'] = globalEvents.subscribe( + apinamespace + '.parentresized' + , (function(eventTokens, $parent, originalParentWidth, sizeRatio){ + 'use strict' + + return function(){ + 'use strict' + + var w = $parent.width() + if (w !== originalParentWidth) { + + // UNsubscribing this particular instance of signature pad only. + // there is a separate `eventTokens` per each instance of signature pad + for (var key in eventTokens){ + if (eventTokens.hasOwnProperty(key)) { + globalEvents.unsubscribe(eventTokens[key]) + delete eventTokens[key] + } + } + + var settings = jSignatureInstance.settings + jSignatureInstance.$parent.children().remove() + for (var key in jSignatureInstance){ + if (jSignatureInstance.hasOwnProperty(key)) { + delete jSignatureInstance[key] + } + } + + // scale data to new signature pad size + settings.data = (function(data, scale){ + var newData = [] + var o, i, l, j, m, stroke + for ( i = 0, l = data.length; i < l; i++) { + stroke = data[i] + + o = {'x':[],'y':[]} + + for ( j = 0, m = stroke.x.length; j < m; j++) { + o.x.push(stroke.x[j] * scale) + o.y.push(stroke.y[j] * scale) + } + + newData.push(o) + } + return newData + })( + settings.data + , w * 1.0 / originalParentWidth + ) + + $parent[apinamespace](settings) + } + } + })( + this.eventTokens + , this.$parent + , this.$parent.width() + , this.canvas.width * 1.0 / this.canvas.height + ) + ) + } +} + + +function jSignatureClass(parent, options, instanceExtensions) { + + var $parent = this.$parent = $(parent) + , eventTokens = this.eventTokens = {} + , events = this.events = new PubSubClass(this) + , globalEvents = $.fn[apinamespace]('globalEvents') + , settings = { + 'width' : 'ratio' + ,'height' : 'ratio' + ,'sizeRatio': 4 // only used when height = 'ratio' + ,'color' : '#000' + ,'background-color': '#fff' + ,'decor-color': '#eee' + ,'lineWidth' : 0 + ,'minFatFingerCompensation' : -10 + ,'showUndoButton': false + ,'data': [] + } + $.extend(settings, getColors($parent)) + if (options) { + $.extend(settings, options) + } + this.settings = settings + + for (var extensionName in instanceExtensions){ + if (instanceExtensions.hasOwnProperty(extensionName)) { + instanceExtensions[extensionName].call(this, extensionName) + } + } + + this.events.publish(apinamespace+'.initializing') + + // these, when enabled, will hover above the sig area. Hence we append them to DOM before canvas. + this.$controlbarUpper = (function(){ + var controlbarstyle = 'padding:0 !important;margin:0 !important;'+ + 'width: 100% !important; height: 0 !important;'+ + 'margin-top:-1em !important;margin-bottom:1em !important;' + return $('
').appendTo($parent) + })(); + + this.isCanvasEmulator = false // will be flipped by initializer when needed. + var canvas = this.canvas = this.initializeCanvas(settings) + , $canvas = $(canvas) + + this.$controlbarLower = (function(){ + var controlbarstyle = 'padding:0 !important;margin:0 !important;'+ + 'width: 100% !important; height: 0 !important;'+ + 'margin-top:-1.5em !important;margin-bottom:1.5em !important;' + return $('
').appendTo($parent) + })(); + + this.canvasContext = canvas.getContext("2d") + + // Most of our exposed API will be looking for this: + $canvas.data(apinamespace + '.this', this) + + + settings.lineWidth = (function(defaultLineWidth, canvasWidth){ + if (!defaultLineWidth){ + return Math.max( + Math.round(canvasWidth / 400) /*+1 pixel for every extra 300px of width.*/ + , 2 /* minimum line width */ + ) + } else { + return defaultLineWidth + } + })(settings.lineWidth, canvas.width); + + this.lineCurveThreshold = settings.lineWidth * 3 + + // Add custom class if defined + if(settings.cssclass && $.trim(settings.cssclass) != "") { + $canvas.addClass(settings.cssclass) + } + + // used for shifting the drawing point up on touch devices, so one can see the drawing above the finger. + this.fatFingerCompensation = 0 + + var movementHandlers = (function(jSignatureInstance) { + + //================================ + // mouse down, move, up handlers: + + // shifts - adjustment values in viewport pixels drived from position of canvas on the page + var shiftX + , shiftY + , setStartValues = function(){ + var tos = $(jSignatureInstance.canvas).offset() + shiftX = tos.left * -1 + shiftY = tos.top * -1 + } + , getPointFromEvent = function(e) { + var firstEvent = (e.changedTouches && e.changedTouches.length > 0 ? e.changedTouches[0] : e) + // All devices i tried report correct coordinates in pageX,Y + // Android Chrome 2.3.x, 3.1, 3.2., Opera Mobile, safari iOS 4.x, + // Windows: Chrome, FF, IE9, Safari + // None of that scroll shift calc vs screenXY other sigs do is needed. + // ... oh, yeah, the "fatFinger.." is for tablets so that people see what they draw. + return new Point( + Math.round(firstEvent.pageX + shiftX) + , Math.round(firstEvent.pageY + shiftY) + jSignatureInstance.fatFingerCompensation + ) + } + , timer = new KickTimerClass( + 750 + , function() { jSignatureInstance.dataEngine.endStroke() } + ) + + this.drawEndHandler = function(e) { + try { e.preventDefault() } catch (ex) {} + timer.clear() + jSignatureInstance.dataEngine.endStroke() + } + this.drawStartHandler = function(e) { + e.preventDefault() + // for performance we cache the offsets + // we recalc these only at the beginning the stroke + setStartValues() + jSignatureInstance.dataEngine.startStroke( getPointFromEvent(e) ) + timer.kick() + } + this.drawMoveHandler = function(e) { + e.preventDefault() + if (!jSignatureInstance.dataEngine.inStroke){ + return + } + jSignatureInstance.dataEngine.addToStroke( getPointFromEvent(e) ) + timer.kick() + } + + return this + + }).call( {}, this ) + + // + //================================ + + ;(function(drawEndHandler, drawStartHandler, drawMoveHandler) { + var canvas = this.canvas + , $canvas = $(canvas) + , undef + if (this.isCanvasEmulator){ + $canvas.bind('mousemove.'+apinamespace, drawMoveHandler) + $canvas.bind('mouseup.'+apinamespace, drawEndHandler) + $canvas.bind('mousedown.'+apinamespace, drawStartHandler) + } else { + canvas.ontouchstart = function(e) { + canvas.onmousedown = undef + canvas.onmouseup = undef + canvas.onmousemove = undef + + this.fatFingerCompensation = ( + settings.minFatFingerCompensation && + settings.lineWidth * -3 > settings.minFatFingerCompensation + ) ? settings.lineWidth * -3 : settings.minFatFingerCompensation + + drawStartHandler(e) + + canvas.ontouchend = drawEndHandler + canvas.ontouchstart = drawStartHandler + canvas.ontouchmove = drawMoveHandler + } + canvas.onmousedown = function(e) { + canvas.ontouchstart = undef + canvas.ontouchend = undef + canvas.ontouchmove = undef + + drawStartHandler(e) + + canvas.onmousedown = drawStartHandler + canvas.onmouseup = drawEndHandler + canvas.onmousemove = drawMoveHandler + } + } + }).call( + this + , movementHandlers.drawEndHandler + , movementHandlers.drawStartHandler + , movementHandlers.drawMoveHandler + ) + + //========================================= + // various event handlers + + // on mouseout + mouseup canvas did not know that mouseUP fired. Continued to draw despite mouse UP. + // it is bettr than + // $canvas.bind('mouseout', drawEndHandler) + // because we don't want to break the stroke where user accidentally gets ouside and wants to get back in quickly. + eventTokens[apinamespace + '.windowmouseup'] = globalEvents.subscribe( + apinamespace + '.windowmouseup' + , movementHandlers.drawEndHandler + ) + + this.events.publish(apinamespace+'.attachingEventHandlers') + + // If we have proportional width, we sign up to events broadcasting "window resized" and checking if + // parent's width changed. If so, we (1) extract settings + data from current signature pad, + // (2) remove signature pad from parent, and (3) reinit new signature pad at new size with same settings, (rescaled) data. + conditionallyLinkCanvasResizeToWindowResize.call( + this + , this + , settings.width.toString(10) + , apinamespace, globalEvents + ) + + // end of event handlers. + // =============================== + + this.resetCanvas(settings.data) + + // resetCanvas renders the data on the screen and fires ONE "change" event + // if there is data. If you have controls that rely on "change" firing + // attach them to something that runs before this.resetCanvas, like + // apinamespace+'.attachingEventHandlers' that fires a bit higher. + this.events.publish(apinamespace+'.initialized') + + return this +} // end of initBase + +//========================================================================= +// jSignatureClass's methods and supporting fn's + +jSignatureClass.prototype.resetCanvas = function(data){ + var canvas = this.canvas + , settings = this.settings + , ctx = this.canvasContext + , isCanvasEmulator = this.isCanvasEmulator + + , cw = canvas.width + , ch = canvas.height + + // preparing colors, drawing area + + ctx.clearRect(0, 0, cw + 30, ch + 30) + + ctx.shadowColor = ctx.fillStyle = settings['background-color'] + if (isCanvasEmulator){ + // FLashCanvas fills with Black by default, covering up the parent div's background + // hence we refill + ctx.fillRect(0,0,cw + 30, ch + 30) + } + + ctx.lineWidth = Math.ceil(parseInt(settings.lineWidth, 10)) + ctx.lineCap = ctx.lineJoin = "round" + + // signature line + ctx.strokeStyle = settings['decor-color'] + ctx.shadowOffsetX = 0 + ctx.shadowOffsetY = 0 + var lineoffset = Math.round( ch / 5 ) + basicLine(ctx, lineoffset * 1.5, ch - lineoffset, cw - (lineoffset * 1.5), ch - lineoffset) + ctx.strokeStyle = settings.color + + if (!isCanvasEmulator){ + ctx.shadowColor = ctx.strokeStyle + ctx.shadowOffsetX = ctx.lineWidth * 0.5 + ctx.shadowOffsetY = ctx.lineWidth * -0.6 + ctx.shadowBlur = 0 + } + + // setting up new dataEngine + + if (!data) { data = [] } + + var dataEngine = this.dataEngine = new DataEngine( + data + , this + , strokeStartCallback + , strokeAddCallback + , strokeEndCallback + ) + + settings.data = data // onwindowresize handler uses it, i think. + $(canvas).data(apinamespace+'.data', data) + .data(apinamespace+'.settings', settings) + + // we fire "change" event on every change in data. + // setting this up: + dataEngine.changed = (function(target, events, apinamespace) { + 'use strict' + return function() { + events.publish(apinamespace+'.change') + target.trigger('change') + } + })(this.$parent, this.events, apinamespace) + // let's trigger change on all data reloads + dataEngine.changed() + + // import filters will be passing this back as indication of "we rendered" + return true +} + +function initializeCanvasEmulator(canvas){ + if (canvas.getContext){ + return false + } else { + // for cases when jSignature, FlashCanvas is inserted + // from one window into another (child iframe) + // 'window' and 'FlashCanvas' may be stuck behind + // in that other parent window. + // we need to find it + var window = canvas.ownerDocument.parentWindow + var FC = window.FlashCanvas ? + canvas.ownerDocument.parentWindow.FlashCanvas : + ( + typeof FlashCanvas === "undefined" ? + undefined : + FlashCanvas + ) + + if (FC) { + canvas = FC.initElement(canvas) + + var zoom = 1 + // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom. + // It matches pixel-to-pixel to screen instead. + // Since we are targeting ONLY IE 7, 8 with FlashCanvas, we will test the zoom only the IE8, IE7 way + if (window && window.screen && window.screen.deviceXDPI && window.screen.logicalXDPI){ + zoom = window.screen.deviceXDPI * 1.0 / window.screen.logicalXDPI + } + if (zoom !== 1){ + try { + // We effectively abuse the brokenness of FlashCanvas and force the flash rendering surface to + // occupy larger pixel dimensions than the wrapping, scaled up DIV and Canvas elems. + $(canvas).children('object').get(0).resize(Math.ceil(canvas.width * zoom), Math.ceil(canvas.height * zoom)) + // And by applying "scale" transformation we can talk "browser pixels" to FlashCanvas + // and have it translate the "browser pixels" to "screen pixels" + canvas.getContext('2d').scale(zoom, zoom) + // Note to self: don't reuse Canvas element. Repeated "scale" are cumulative. + } catch (ex) {} + } + return true + } else { + throw new Error("Canvas element does not support 2d context. jSignature cannot proceed.") + } + } + +} + +jSignatureClass.prototype.initializeCanvas = function(settings) { + // =========== + // Init + Sizing code + + var canvas = document.createElement('canvas') + , $canvas = $(canvas) + + // We cannot work with circular dependency + if (settings.width === settings.height && settings.height === 'ratio') { + settings.width = '100%' + } + + $canvas.css( + 'margin' + , 0 + ).css( + 'padding' + , 0 + ).css( + 'border' + , 'none' + ).css( + 'height' + , settings.height === 'ratio' || !settings.height ? 1 : settings.height.toString(10) + ).css( + 'width' + , settings.width === 'ratio' || !settings.width ? 1 : settings.width.toString(10) + ) + + $canvas.appendTo(this.$parent) + + // we could not do this until canvas is rendered (appended to DOM) + if (settings.height === 'ratio') { + $canvas.css( + 'height' + , Math.round( $canvas.width() / settings.sizeRatio ) + ) + } else if (settings.width === 'ratio') { + $canvas.css( + 'width' + , Math.round( $canvas.height() * settings.sizeRatio ) + ) + } + + $canvas.addClass(apinamespace) + + // canvas's drawing area resolution is independent from canvas's size. + // pixels are just scaled up or down when internal resolution does not + // match external size. So... + + canvas.width = $canvas.width() + canvas.height = $canvas.height() + + // Special case Sizing code + + this.isCanvasEmulator = initializeCanvasEmulator(canvas) + + // End of Sizing Code + // =========== + + // normally select preventer would be short, but + // Canvas emulator on IE does NOT provide value for Event. Hence this convoluted line. + canvas.onselectstart = function(e){if(e && e.preventDefault){e.preventDefault()}; if(e && e.stopPropagation){e.stopPropagation()}; return false;} + + return canvas +} + + +var GlobalJSignatureObjectInitializer = function(window){ + + var globalEvents = new PubSubClass() + + // common "window resized" event listener. + // jSignature instances will subscribe to this chanel. + // to resize themselves when needed. + ;(function(globalEvents, apinamespace, $, window){ + 'use strict' + + var resizetimer + , runner = function(){ + globalEvents.publish( + apinamespace + '.parentresized' + ) + } + + // jSignature knows how to resize its content when its parent is resized + // window resize is the only way we can catch resize events though... + $(window).bind('resize.'+apinamespace, function(){ + if (resizetimer) { + clearTimeout(resizetimer) + } + resizetimer = setTimeout( + runner + , 500 + ) + }) + // when mouse exists canvas element and "up"s outside, we cannot catch it with + // callbacks attached to canvas. This catches it outside. + .bind('mouseup.'+apinamespace, function(e){ + globalEvents.publish( + apinamespace + '.windowmouseup' + ) + }) + + })(globalEvents, apinamespace, $, window) + + var jSignatureInstanceExtensions = { + + 'exampleExtension':function(extensionName){ + // we are called very early in instance's life. + // right after the settings are resolved and + // jSignatureInstance.events is created + // and right before first ("jSignature.initializing") event is called. + // You don't really need to manupilate + // jSignatureInstance directly, just attach + // a bunch of events to jSignatureInstance.events + // (look at the source of jSignatureClass to see when these fire) + // and your special pieces of code will attach by themselves. + + // this function runs every time a new instance is set up. + // this means every var you create will live only for one instance + // unless you attach it to something outside, like "window." + // and pick it up later from there. + + // when globalEvents' events fire, 'this' is globalEvents object + // when jSignatureInstance's events fire, 'this' is jSignatureInstance + + // Here, + // this = is new jSignatureClass's instance. + + // The way you COULD approch setting this up is: + // if you have multistep set up, attach event to "jSignature.initializing" + // that attaches other events to be fired further lower the init stream. + // Or, if you know for sure you rely on only one jSignatureInstance's event, + // just attach to it directly + + this.events.subscribe( + // name of the event + apinamespace + '.initializing' + // event handlers, can pass args too, but in majority of cases, + // 'this' which is jSignatureClass object instance pointer is enough to get by. + , function(){ + if (this.settings.hasOwnProperty('non-existent setting category?')) { + console.log(extensionName + ' is here') + } + } + ) + } + + } + + var exportplugins = { + 'default':function(data){return this.toDataURL()} + , 'native':function(data){return data} + , 'image':function(data){ + /*this = canvas elem */ + var imagestring = this.toDataURL() + + if (typeof imagestring === 'string' && + imagestring.length > 4 && + imagestring.slice(0,5) === 'data:' && + imagestring.indexOf(',') !== -1){ + + var splitterpos = imagestring.indexOf(',') + + return [ + imagestring.slice(5, splitterpos) + , imagestring.substr(splitterpos + 1) + ] + } + return [] + } + } + + // will be part of "importplugins" + function _renderImageOnCanvas( data, formattype, rerendercallable ) { + 'use strict' + // #1. Do NOT rely on this. No worky on IE + // (url max len + lack of base64 decoder + possibly other issues) + // #2. This does NOT affect what is captured as "signature" as far as vector data is + // concerned. This is treated same as "signature line" - i.e. completely ignored + // the only time you see imported image data exported is if you export as image. + + // we do NOT call rerendercallable here (unlike in other import plugins) + // because importing image does absolutely nothing to the underlying vector data storage + // This could be a way to "import" old signatures stored as images + // This could also be a way to import extra decor into signature area. + + var img = new Image() + // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div. + , c = this + + img.onload = function() { + var ctx = c.getContext("2d").drawImage( + img, 0, 0 + , ( img.width < c.width) ? img.width : c.width + , ( img.height < c.height) ? img.height : c.height + ) + } + + img.src = 'data:' + formattype + ',' + data + } + + var importplugins = { + 'native':function(data, formattype, rerendercallable){ + // we expect data as Array of objects of arrays here - whatever 'default' EXPORT plugin spits out. + // returning Truthy to indicate we are good, all updated. + rerendercallable( data ) + } + , 'image': _renderImageOnCanvas + , 'image/png;base64': _renderImageOnCanvas + , 'image/jpeg;base64': _renderImageOnCanvas + , 'image/jpg;base64': _renderImageOnCanvas + } + + function _clearDrawingArea( data ) { + this.find('canvas.'+apinamespace) + .add(this.filter('canvas.'+apinamespace)) + .data(apinamespace+'.this').resetCanvas( data ) + return this + } + + function _setDrawingData( data, formattype ) { + var undef + + if (formattype === undef && typeof data === 'string' && data.substr(0,5) === 'data:') { + formattype = data.slice(5).split(',')[0] + // 5 chars of "data:" + mimetype len + 1 "," char = all skipped. + data = data.slice(6 + formattype.length) + if (formattype === data) return + } + + var $canvas = this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace)) + + if (!importplugins.hasOwnProperty(formattype)){ + throw new Error(apinamespace + " is unable to find import plugin with for format '"+ String(formattype) +"'") + } else if ($canvas.length !== 0){ + importplugins[formattype].call( + $canvas[0] + , data + , formattype + , (function(jSignatureInstance){ + return function(){ return jSignatureInstance.resetCanvas.apply(jSignatureInstance, arguments) } + })($canvas.data(apinamespace+'.this')) + ) + } + + return this + } + + var elementIsOrphan = function(e){ + var topOfDOM = false + e = e.parentNode + while (e && !topOfDOM){ + topOfDOM = $(e).find(".oe_form") + e = e.parentNode + } + return !topOfDOM + } + + //These are exposed as methods under $obj.jSignature('methodname', *args) + var plugins = {'export':exportplugins, 'import':importplugins, 'instance': jSignatureInstanceExtensions} + , methods = { + 'init' : function( options ) { + return this.each( function() { + if (!elementIsOrphan(this)) { + new jSignatureClass(this, options, jSignatureInstanceExtensions) + } + }) + } + , 'getSettings' : function() { + return this.find('canvas.'+apinamespace) + .add(this.filter('canvas.'+apinamespace)) + .data(apinamespace+'.this').settings + } + // around since v1 + , 'clear' : _clearDrawingArea + // was mistakenly introduced instead of 'clear' in v2 + , 'reset' : _clearDrawingArea + , 'addPlugin' : function(pluginType, pluginName, callable){ + if (plugins.hasOwnProperty(pluginType)){ + plugins[pluginType][pluginName] = callable + } + return this + } + , 'listPlugins' : function(pluginType){ + var answer = [] + if (plugins.hasOwnProperty(pluginType)){ + var o = plugins[pluginType] + for (var k in o){ + if (o.hasOwnProperty(k)){ + answer.push(k) + } + } + } + return answer + } + , 'getData' : function( formattype ) { + var undef, $canvas=this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace)) + if (formattype === undef) formattype = 'default' + if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){ + return exportplugins[formattype].call( + $canvas.get(0) // canvas dom elem + , $canvas.data(apinamespace+'.data') // raw signature data as array of objects of arrays + ) + } + } + // around since v1. Took only one arg - data-url-formatted string with (likely png of) signature image + , 'importData' : _setDrawingData + // was mistakenly introduced instead of 'importData' in v2 + , 'setData' : _setDrawingData + // this is one and same instance for all jSignature. + , 'globalEvents' : function(){return globalEvents} + // there will be a separate one for each jSignature instance. + , 'events' : function() { + return this.find('canvas.'+apinamespace) + .add(this.filter('canvas.'+apinamespace)) + .data(apinamespace+'.this').events + } + } // end of methods declaration. + + $.fn[apinamespace] = function(method) { + 'use strict' + if ( !method || typeof method === 'object' ) { + return methods.init.apply( this, arguments ) + } else if ( typeof method === 'string' && methods[method] ) { + return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )) + } else { + $.error( 'Method ' + String(method) + ' does not exist on jQuery.' + apinamespace ) + } + } + +} // end of GlobalJSignatureObjectInitializer + +GlobalJSignatureObjectInitializer(window) + +})(); \ No newline at end of file diff --git a/web_widget_digitized_signature/static/src/js/digital_sign.js b/web_widget_digitized_signature/static/src/js/digital_sign.js new file mode 100644 index 00000000..468a644a --- /dev/null +++ b/web_widget_digitized_signature/static/src/js/digital_sign.js @@ -0,0 +1,122 @@ +odoo.define('web_widget_digitized_signature.web_digital_sign', function(require) { + "use strict"; + + var core = require('web.core'); + var FormView = require('web.FormView'); + var utils = require('web.utils'); + var session = require('web.session'); + var Model = require('web.Model'); + + var _t = core._t; + var QWeb = core.qweb; + + var FieldSignature = core.form_widget_registry.map.image.extend({ + template: 'FieldSignature', + placeholder: "/web/static/src/img/placeholder.png", + initialize_content: function() { + var self = this; + this.$el.find('> img').remove(); + this.$el.find('.signature > canvas').remove(); + var sign_options = {'decor-color' : '#D1D0CE', 'color': '#000', 'background-color': '#fff','height':'150','width':'550'}; + this.$el.find(".signature").jSignature("init",sign_options); + this.$el.find(".signature").attr({"tabindex": "0",'height':"100"}); + this.empty_sign = this.$el.find(".signature").jSignature("getData",'image'); + this.$el.find('#sign_clean').click(this.on_clear_sign); + this.$el.find('.save_sign').click(this.on_save_sign); + }, + on_clear_sign: function() { + var self = this; + this.$el.find(".signature > canvas").remove(); + this.$el.find('> img').remove(); + this.$el.find(".signature").attr("tabindex", "0"); + var sign_options = {'decor-color' : '#D1D0CE', 'color': '#000', 'background-color': '#fff','height':'150','width':'550','clear': true}; + this.$el.find(".signature").jSignature(sign_options); + this.$el.find(".signature").focus(); + self.set('value', false); + }, + on_save_sign: function(value_) { + var self = this; + this.$el.find('> img').remove(); + var signature = self.$el.find(".signature").jSignature("getData",'image'); + var is_empty = signature + ? self.empty_sign[1] === signature[1] + : false; + if (! is_empty && typeof signature !== "undefined" && signature[1]) { + self.set('value',signature[1]); + } + }, + render_value: function() { + var self = this; + var url = this.placeholder; + if (this.get('value') && !utils.is_bin_size(this.get('value'))) { + url = 'data:image/png;base64,' + this.get('value'); + } else if (this.get('value')) { + url = this.session.url('/web/binary/image', { + model: this.view.dataset.model, + id: JSON.stringify(this.view.datarecord.id || null), + field: this.options.preview_image + ? this.options.preview_image + : this.name, + t: new Date().getTime() + }); + } else { + url = this.placeholder; + } + if (this.view.get("actual_mode") === 'view') { + var $img = $(QWeb.render("FieldBinaryImage-extend", { widget: this, url: url })); + this.$el.find('> img').remove(); + this.$el.find(".signature").hide(); + this.$el.prepend($img); + $img.load(function() { + if (! self.options.size) { + return; + } + $img.css("max-width", "" + self.options.size[0] + "px"); + $img.css("max-height", "" + self.options.size[1] + "px"); + $img.css("margin-left", "" + (self.options.size[0] - $img.width()) / 2 + "px"); + $img.css("margin-top", "" + (self.options.size[1] - $img.height()) / 2 + "px"); + }); + $img.on('error', function() { + $img.attr('src', self.placeholder); + self.do_warn(_t("Image"), _t("Could not display the selected image.")); + }); + } else if (this.view.get("actual_mode") === 'edit') { + this.$el.find('> img').remove(); + if (this.get('value')) { + var field_name = this.options.preview_image + ? this.options.preview_image + : this.name; + new Model(this.view.dataset.model).call("read", [this.view.datarecord.id, [field_name]]).done(function(data) { + if (data) { + var field_desc = _.values(_.pick(data, field_name)); + self.$el.find(".signature").jSignature("reset"); + self.$el.find(".signature").jSignature("setData",'data:image/png;base64,'+field_desc[0]); + } + }); + } else { + this.$el.find('> img').remove(); + this.$el.find('.signature > canvas').remove(); + var sign_options = {'decor-color' : '#D1D0CE', 'color': '#000','background-color': '#fff','height':'150','width':'550'}; + this.$el.find(".signature").jSignature("init",sign_options); + } + } else if (this.view.get("actual_mode") === 'create') { + this.$el.find('> img').remove(); + this.$el.find('> canvas').remove(); + if (!this.get('value')) { + this.$el.find(".signature").empty().jSignature("init",{'decor-color' : '#D1D0CE', 'color': '#000','background-color': '#fff','height':'150','width':'550'}); + } + } + } + }); + + core.form_widget_registry.add('signature', FieldSignature); + + FormView.include({ + save: function() { + this.$el.find('.save_sign').click(); + return this._super.apply(this, arguments); + } + }); + +}); + diff --git a/web_widget_digitized_signature/static/src/xml/digital_sign.xml b/web_widget_digitized_signature/static/src/xml/digital_sign.xml new file mode 100644 index 00000000..df4e8e22 --- /dev/null +++ b/web_widget_digitized_signature/static/src/xml/digital_sign.xml @@ -0,0 +1,24 @@ + + diff --git a/web_widget_digitized_signature/views/res_users_view.xml b/web_widget_digitized_signature/views/res_users_view.xml new file mode 100644 index 00000000..3971c209 --- /dev/null +++ b/web_widget_digitized_signature/views/res_users_view.xml @@ -0,0 +1,27 @@ + + + + + inherited.res.users.form + res.users + + + + + + + + res.users.preferences.form + res.users + + + + + + + + diff --git a/web_widget_digitized_signature/views/web_digital_sign_view.xml b/web_widget_digitized_signature/views/web_digital_sign_view.xml new file mode 100644 index 00000000..4331c08c --- /dev/null +++ b/web_widget_digitized_signature/views/web_digital_sign_view.xml @@ -0,0 +1,11 @@ + + + + + + From 730715577d27098d6980494fa7001cf88afc658b Mon Sep 17 00:00:00 2001 From: "Pedro M. Baeza" Date: Sat, 29 Jul 2017 13:30:43 +0200 Subject: [PATCH 2/7] [IMP] web_widget_digitized_signature: Add tests --- .../models/res_users.py | 17 ++++++++-- .../tests/__init__.py | 4 +++ .../tests/test_signature_tracking.py | 34 +++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 web_widget_digitized_signature/tests/__init__.py create mode 100644 web_widget_digitized_signature/tests/test_signature_tracking.py diff --git a/web_widget_digitized_signature/models/res_users.py b/web_widget_digitized_signature/models/res_users.py index c1275b66..7816ba91 100644 --- a/web_widget_digitized_signature/models/res_users.py +++ b/web_widget_digitized_signature/models/res_users.py @@ -4,13 +4,24 @@ # Copyright 2017 Tecnativa - Vicent Cubells # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import models, fields +from openerp import api, fields, models -class Users(models.Model): +class ResUsers(models.Model): _name = 'res.users' - _inherit = 'res.users' + _inherit = ['res.users', 'mail.thread'] signature_image = fields.Binary( string='Signature', ) + + @api.model + def create(self, vals): + res = super(ResUsers, self).create(vals) + res._track_signature(vals, 'signature') + return res + + @api.multi + def write(self, vals): + self._track_signature(vals, 'signature') + return super(ResUsers, self).write(vals) diff --git a/web_widget_digitized_signature/tests/__init__.py b/web_widget_digitized_signature/tests/__init__.py new file mode 100644 index 00000000..3dc777d5 --- /dev/null +++ b/web_widget_digitized_signature/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_signature_tracking diff --git a/web_widget_digitized_signature/tests/test_signature_tracking.py b/web_widget_digitized_signature/tests/test_signature_tracking.py new file mode 100644 index 00000000..1b6ee0af --- /dev/null +++ b/web_widget_digitized_signature/tests/test_signature_tracking.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp.tests import common + + +class TestSignatureTracking(common.SavepointCase): + @classmethod + def setUpClass(cls): + super(TestSignatureTracking, cls).setUpClass() + cls.user = cls.env.user + cls.user.lang = 'en_US' + # Simple 1x1 transparent base64 encoded GIF + cls.image = 'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' + cls.attachment_obj = cls.env['ir.attachment'] + cls.message_obj = cls.env['mail.message'] + + def test_signature_tracking(self): + """We have to test in a tricky way, as res.users doesn't allow a + direct chatter""" + prev_attachment_num = self.attachment_obj.search_count([]) + prev_messages = self.message_obj.search([]) + self.user.signature = self.image + current_attachment_num = self.attachment_obj.search_count([]) + self.assertEqual(current_attachment_num - prev_attachment_num, 1) + current_messages = self.message_obj.search([]) + message = current_messages - prev_messages + self.assertIn('Signature has been created.', message.body) + prev_messages = current_messages + self.user.signature = False + current_messages = self.message_obj.search([]) + message = current_messages - prev_messages + self.assertIn('Signature has been deleted.', message.body) From 30fd2c8549f073e099e7b0a85d1c282822d6c20c Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sat, 5 Aug 2017 13:35:34 +0200 Subject: [PATCH 3/7] OCA Transbot updated translations from Transifex --- web_widget_digitized_signature/i18n/ca.po | 106 +++++++++++++++++++++ web_widget_digitized_signature/i18n/es.po | 51 ++++++++--- web_widget_digitized_signature/i18n/fr.po | 107 ++++++++++++++++++++++ web_widget_digitized_signature/i18n/sl.po | 106 +++++++++++++++++++++ 4 files changed, 358 insertions(+), 12 deletions(-) create mode 100644 web_widget_digitized_signature/i18n/ca.po create mode 100644 web_widget_digitized_signature/i18n/fr.po create mode 100644 web_widget_digitized_signature/i18n/sl.po diff --git a/web_widget_digitized_signature/i18n/ca.po b/web_widget_digitized_signature/i18n/ca.po new file mode 100644 index 00000000..d332596d --- /dev/null +++ b/web_widget_digitized_signature/i18n/ca.po @@ -0,0 +1,106 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +# Translators: +# Marc Tormo i Bochaca , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0c\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-30 02:50+0000\n" +"PO-Revision-Date: 2017-07-30 02:50+0000\n" +"Last-Translator: Marc Tormo i Bochaca , 2017\n" +"Language-Team: Catalan (https://www.transifex.com/oca/teams/23907/ca/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ca\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_uid +msgid "Created by" +msgstr "Creat per" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_date +msgid "Created on" +msgstr "Creat a " + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_uid +msgid "Last Updated by" +msgstr "Última actualització per" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_date +msgid "Last Updated on" +msgstr "Última actualització a " + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "Usuaris" diff --git a/web_widget_digitized_signature/i18n/es.po b/web_widget_digitized_signature/i18n/es.po index 0f1120bf..1b92707d 100644 --- a/web_widget_digitized_signature/i18n/es.po +++ b/web_widget_digitized_signature/i18n/es.po @@ -1,19 +1,22 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * web_widget_digitized_signature -# +# * web_widget_digitized_signature +# +# Translators: +# OCA Transbot , 2017 msgid "" msgstr "" "Project-Id-Version: Odoo Server 9.0c\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-11 04:42+0000\n" -"PO-Revision-Date: 2017-07-11 04:42+0000\n" -"Last-Translator: <>\n" -"Language-Team: \n" +"POT-Creation-Date: 2017-07-30 02:50+0000\n" +"PO-Revision-Date: 2017-07-30 02:50+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Plural-Forms: \n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: web_widget_digitized_signature #. openerp-web @@ -30,7 +33,17 @@ msgid "Could not display the selected image." msgstr "No se puede mostrar la imagen seleccionada." #. module: web_widget_digitized_signature -#: code:addons/web_widget_digitized_signature/models/mail_thread.py:26 +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_uid +msgid "Created by" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_date +msgid "Created on" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 #, python-format msgid "Deletion date: %s" msgstr "Fecha de eliminación: %s" @@ -42,6 +55,11 @@ msgstr "Fecha de eliminación: %s" msgid "Draw your signature" msgstr "Dibuje su firma" +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "" + #. module: web_widget_digitized_signature #. openerp-web #: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 @@ -49,25 +67,35 @@ msgstr "Dibuje su firma" msgid "Image" msgstr "Imagen" +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_uid +msgid "Last Updated by" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_date +msgid "Last Updated on" +msgstr "" + #. module: web_widget_digitized_signature #: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image msgid "Signature" msgstr "Firma" #. module: web_widget_digitized_signature -#: code:addons/web_widget_digitized_signature/models/mail_thread.py:23 +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 #, python-format msgid "Signature date: %s" msgstr "Fecha de la firma: %s" #. module: web_widget_digitized_signature -#: code:addons/web_widget_digitized_signature/models/mail_thread.py:21 +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 #, python-format msgid "Signature has been created." msgstr "La firma se ha creado." #. module: web_widget_digitized_signature -#: code:addons/web_widget_digitized_signature/models/mail_thread.py:25 +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 #, python-format msgid "Signature has been deleted." msgstr "La firma se ha eliminado." @@ -76,4 +104,3 @@ msgstr "La firma se ha eliminado." #: model:ir.model,name:web_widget_digitized_signature.model_res_users msgid "Users" msgstr "Usuarios" - diff --git a/web_widget_digitized_signature/i18n/fr.po b/web_widget_digitized_signature/i18n/fr.po new file mode 100644 index 00000000..485a8326 --- /dev/null +++ b/web_widget_digitized_signature/i18n/fr.po @@ -0,0 +1,107 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +# Translators: +# OCA Transbot , 2017 +# Quentin THEURET , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0c\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-30 02:50+0000\n" +"PO-Revision-Date: 2017-07-30 02:50+0000\n" +"Last-Translator: Quentin THEURET , 2017\n" +"Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "Effacer" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "Impossible d'afficher l'image sélectionnée." + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_date +msgid "Created on" +msgstr "Créé le" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "Date de suppression : %s" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "Dessinez votre signature" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "Fil du courriel" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "Image" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_uid +msgid "Last Updated by" +msgstr "Mis à jour par" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_date +msgid "Last Updated on" +msgstr "Mis à jour le" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "Signature" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "Date de la signature : %s" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "La signature a été créée." + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "La signature a été supprimée." + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "Utilisateurs" diff --git a/web_widget_digitized_signature/i18n/sl.po b/web_widget_digitized_signature/i18n/sl.po new file mode 100644 index 00000000..aa8fb8de --- /dev/null +++ b/web_widget_digitized_signature/i18n/sl.po @@ -0,0 +1,106 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +# Translators: +# OCA Transbot , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0c\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-07-30 02:50+0000\n" +"PO-Revision-Date: 2017-07-30 02:50+0000\n" +"Last-Translator: OCA Transbot , 2017\n" +"Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: sl\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_uid +msgid "Created by" +msgstr "Ustvaril" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_date +msgid "Created on" +msgstr "Ustvarjeno" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_uid +msgid "Last Updated by" +msgstr "Zadnjič posodobil" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_date +msgid "Last Updated on" +msgstr "Zadnjič posodobljeno" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "" From 85b0b958be040f54ce18391435fb572ca1700530 Mon Sep 17 00:00:00 2001 From: cubells Date: Fri, 11 Aug 2017 12:30:22 +0200 Subject: [PATCH 4/7] [FIX] Incorrect field name to track signature (#691) --- web_widget_digitized_signature/models/res_users.py | 4 ++-- .../tests/test_signature_tracking.py | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/web_widget_digitized_signature/models/res_users.py b/web_widget_digitized_signature/models/res_users.py index 7816ba91..1feeac91 100644 --- a/web_widget_digitized_signature/models/res_users.py +++ b/web_widget_digitized_signature/models/res_users.py @@ -18,10 +18,10 @@ class ResUsers(models.Model): @api.model def create(self, vals): res = super(ResUsers, self).create(vals) - res._track_signature(vals, 'signature') + res._track_signature(vals, 'signature_image') return res @api.multi def write(self, vals): - self._track_signature(vals, 'signature') + self._track_signature(vals, 'signature_image') return super(ResUsers, self).write(vals) diff --git a/web_widget_digitized_signature/tests/test_signature_tracking.py b/web_widget_digitized_signature/tests/test_signature_tracking.py index 1b6ee0af..d0b3fbe7 100644 --- a/web_widget_digitized_signature/tests/test_signature_tracking.py +++ b/web_widget_digitized_signature/tests/test_signature_tracking.py @@ -9,7 +9,11 @@ class TestSignatureTracking(common.SavepointCase): @classmethod def setUpClass(cls): super(TestSignatureTracking, cls).setUpClass() - cls.user = cls.env.user + cls.user = cls.env['res.users'].create({ + 'name': 'Test User', + 'login': 'Test User', + 'email': 'test@example.com', + }) cls.user.lang = 'en_US' # Simple 1x1 transparent base64 encoded GIF cls.image = 'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' @@ -21,14 +25,14 @@ class TestSignatureTracking(common.SavepointCase): direct chatter""" prev_attachment_num = self.attachment_obj.search_count([]) prev_messages = self.message_obj.search([]) - self.user.signature = self.image + self.user.signature_image = self.image current_attachment_num = self.attachment_obj.search_count([]) self.assertEqual(current_attachment_num - prev_attachment_num, 1) current_messages = self.message_obj.search([]) message = current_messages - prev_messages self.assertIn('Signature has been created.', message.body) prev_messages = current_messages - self.user.signature = False + self.user.signature_image = False current_messages = self.message_obj.search([]) message = current_messages - prev_messages self.assertIn('Signature has been deleted.', message.body) From fa788e011af34139855a324b46ff0625b8ed1957 Mon Sep 17 00:00:00 2001 From: eLBati Date: Mon, 11 Sep 2017 17:15:29 +0200 Subject: [PATCH 5/7] porting web_widget_digitized_signature to 10.0 --- web_widget_digitized_signature/README.rst | 2 +- .../{__openerp__.py => __manifest__.py} | 2 +- web_widget_digitized_signature/models/mail_thread.py | 4 ++-- web_widget_digitized_signature/models/res_users.py | 2 +- .../tests/test_signature_tracking.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename web_widget_digitized_signature/{__openerp__.py => __manifest__.py} (96%) diff --git a/web_widget_digitized_signature/README.rst b/web_widget_digitized_signature/README.rst index 53c47a52..471728f2 100644 --- a/web_widget_digitized_signature/README.rst +++ b/web_widget_digitized_signature/README.rst @@ -30,7 +30,7 @@ Usage .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/162/9.0 + :target: https://runbot.odoo-community.org/runbot/162/10.0 Bug Tracker =========== diff --git a/web_widget_digitized_signature/__openerp__.py b/web_widget_digitized_signature/__manifest__.py similarity index 96% rename from web_widget_digitized_signature/__openerp__.py rename to web_widget_digitized_signature/__manifest__.py index 1f61492d..9d97eeca 100644 --- a/web_widget_digitized_signature/__openerp__.py +++ b/web_widget_digitized_signature/__manifest__.py @@ -6,7 +6,7 @@ { "name": "Web Widget Digitized Signature", - "version": "9.0.1.0.0", + "version": "10.0.0.1.0", "author": "Serpent Consulting Services Pvt. Ltd., " "Agile Business Group, " "Tecnativa, " diff --git a/web_widget_digitized_signature/models/mail_thread.py b/web_widget_digitized_signature/models/mail_thread.py index 63b84251..b6c3ce5d 100644 --- a/web_widget_digitized_signature/models/mail_thread.py +++ b/web_widget_digitized_signature/models/mail_thread.py @@ -5,10 +5,10 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import base64 -from openerp import _, models, fields +from odoo import _, models, fields -class MailThread(models.Model): +class MailThread(models.AbstractModel): _inherit = "mail.thread" def _track_signature(self, values, field): diff --git a/web_widget_digitized_signature/models/res_users.py b/web_widget_digitized_signature/models/res_users.py index 1feeac91..1d6fc437 100644 --- a/web_widget_digitized_signature/models/res_users.py +++ b/web_widget_digitized_signature/models/res_users.py @@ -4,7 +4,7 @@ # Copyright 2017 Tecnativa - Vicent Cubells # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp import api, fields, models +from odoo import api, fields, models class ResUsers(models.Model): diff --git a/web_widget_digitized_signature/tests/test_signature_tracking.py b/web_widget_digitized_signature/tests/test_signature_tracking.py index d0b3fbe7..98d04421 100644 --- a/web_widget_digitized_signature/tests/test_signature_tracking.py +++ b/web_widget_digitized_signature/tests/test_signature_tracking.py @@ -2,7 +2,7 @@ # Copyright 2017 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from openerp.tests import common +from odoo.tests import common class TestSignatureTracking(common.SavepointCase): From 44eb815751266cfd703aeb6e46287674f9c00e9a Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Wed, 17 Jan 2018 10:50:20 +0100 Subject: [PATCH 6/7] OCA Transbot updated translations from Transifex --- web_widget_digitized_signature/i18n/ca.po | 36 +++----- web_widget_digitized_signature/i18n/de.po | 87 ++++++++++++++++++ web_widget_digitized_signature/i18n/es.po | 48 +++------- web_widget_digitized_signature/i18n/fr.po | 36 +++----- web_widget_digitized_signature/i18n/hr.po | 87 ++++++++++++++++++ web_widget_digitized_signature/i18n/lt.po | 87 ++++++++++++++++++ web_widget_digitized_signature/i18n/nl_NL.po | 87 ++++++++++++++++++ web_widget_digitized_signature/i18n/pt_BR.po | 88 +++++++++++++++++++ web_widget_digitized_signature/i18n/sl.po | 39 ++++---- .../i18n/web_widget_digitized_signature.pot | 82 +++++++++++++++++ 10 files changed, 576 insertions(+), 101 deletions(-) create mode 100644 web_widget_digitized_signature/i18n/de.po create mode 100644 web_widget_digitized_signature/i18n/hr.po create mode 100644 web_widget_digitized_signature/i18n/lt.po create mode 100644 web_widget_digitized_signature/i18n/nl_NL.po create mode 100644 web_widget_digitized_signature/i18n/pt_BR.po create mode 100644 web_widget_digitized_signature/i18n/web_widget_digitized_signature.pot diff --git a/web_widget_digitized_signature/i18n/ca.po b/web_widget_digitized_signature/i18n/ca.po index d332596d..013f6da2 100644 --- a/web_widget_digitized_signature/i18n/ca.po +++ b/web_widget_digitized_signature/i18n/ca.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * web_widget_digitized_signature -# +# # Translators: # Marc Tormo i Bochaca , 2017 msgid "" @@ -12,10 +12,10 @@ msgstr "" "PO-Revision-Date: 2017-07-30 02:50+0000\n" "Last-Translator: Marc Tormo i Bochaca , 2017\n" "Language-Team: Catalan (https://www.transifex.com/oca/teams/23907/ca/)\n" +"Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: web_widget_digitized_signature @@ -32,16 +32,6 @@ msgstr "" msgid "Could not display the selected image." msgstr "" -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_uid -msgid "Created by" -msgstr "Creat per" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_date -msgid "Created on" -msgstr "Creat a " - #. module: web_widget_digitized_signature #: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 #, python-format @@ -67,16 +57,6 @@ msgstr "" msgid "Image" msgstr "" -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_uid -msgid "Last Updated by" -msgstr "Última actualització per" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_date -msgid "Last Updated on" -msgstr "Última actualització a " - #. module: web_widget_digitized_signature #: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image msgid "Signature" @@ -104,3 +84,15 @@ msgstr "" #: model:ir.model,name:web_widget_digitized_signature.model_res_users msgid "Users" msgstr "Usuaris" + +#~ msgid "Created by" +#~ msgstr "Creat per" + +#~ msgid "Created on" +#~ msgstr "Creat a " + +#~ msgid "Last Updated by" +#~ msgstr "Última actualització per" + +#~ msgid "Last Updated on" +#~ msgstr "Última actualització a " diff --git a/web_widget_digitized_signature/i18n/de.po b/web_widget_digitized_signature/i18n/de.po new file mode 100644 index 00000000..f371e5ad --- /dev/null +++ b/web_widget_digitized_signature/i18n/de.po @@ -0,0 +1,87 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +# Translators: +# Niki Waibel , 2017 +# Rudolf Schnapka , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"Last-Translator: Rudolf Schnapka , 2018\n" +"Language-Team: German (https://www.transifex.com/oca/teams/23907/de/)\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "bereinigen" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "Kann ausgewähltes Bild nicht anzeigen." + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "Löschdatum %s" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "Ihre Unterschift eindrucken" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "Email-Thema" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "Bild" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "Unterschrift" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "Unterschriftdatum: %s" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "Unterschrift wurde angelegt." + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "Unterschrift wurde entfernt." + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "Benutzer" diff --git a/web_widget_digitized_signature/i18n/es.po b/web_widget_digitized_signature/i18n/es.po index 1b92707d..15b90b93 100644 --- a/web_widget_digitized_signature/i18n/es.po +++ b/web_widget_digitized_signature/i18n/es.po @@ -1,21 +1,21 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * web_widget_digitized_signature -# +# # Translators: -# OCA Transbot , 2017 +# Pedro M. Baeza , 2017 msgid "" msgstr "" -"Project-Id-Version: Odoo Server 9.0c\n" +"Project-Id-Version: Odoo Server 10.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-07-30 02:50+0000\n" -"PO-Revision-Date: 2017-07-30 02:50+0000\n" -"Last-Translator: OCA Transbot , 2017\n" +"POT-Creation-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"Last-Translator: Pedro M. Baeza , 2017\n" "Language-Team: Spanish (https://www.transifex.com/oca/teams/23907/es/)\n" +"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. module: web_widget_digitized_signature @@ -23,37 +23,27 @@ msgstr "" #: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 #, python-format msgid "Clear" -msgstr "Limpiar" +msgstr "" #. module: web_widget_digitized_signature #. openerp-web #: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 #, python-format msgid "Could not display the selected image." -msgstr "No se puede mostrar la imagen seleccionada." - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_uid -msgid "Created by" -msgstr "" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_date -msgid "Created on" msgstr "" #. module: web_widget_digitized_signature #: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 #, python-format msgid "Deletion date: %s" -msgstr "Fecha de eliminación: %s" +msgstr "" #. module: web_widget_digitized_signature #. openerp-web #: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 #, python-format msgid "Draw your signature" -msgstr "Dibuje su firma" +msgstr "" #. module: web_widget_digitized_signature #: model:ir.model,name:web_widget_digitized_signature.model_mail_thread @@ -65,40 +55,30 @@ msgstr "" #: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 #, python-format msgid "Image" -msgstr "Imagen" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_uid -msgid "Last Updated by" -msgstr "" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_date -msgid "Last Updated on" msgstr "" #. module: web_widget_digitized_signature #: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image msgid "Signature" -msgstr "Firma" +msgstr "" #. module: web_widget_digitized_signature #: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 #, python-format msgid "Signature date: %s" -msgstr "Fecha de la firma: %s" +msgstr "" #. module: web_widget_digitized_signature #: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 #, python-format msgid "Signature has been created." -msgstr "La firma se ha creado." +msgstr "" #. module: web_widget_digitized_signature #: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 #, python-format msgid "Signature has been deleted." -msgstr "La firma se ha eliminado." +msgstr "" #. module: web_widget_digitized_signature #: model:ir.model,name:web_widget_digitized_signature.model_res_users diff --git a/web_widget_digitized_signature/i18n/fr.po b/web_widget_digitized_signature/i18n/fr.po index 485a8326..507eb2f4 100644 --- a/web_widget_digitized_signature/i18n/fr.po +++ b/web_widget_digitized_signature/i18n/fr.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * web_widget_digitized_signature -# +# # Translators: # OCA Transbot , 2017 # Quentin THEURET , 2017 @@ -13,10 +13,10 @@ msgstr "" "PO-Revision-Date: 2017-07-30 02:50+0000\n" "Last-Translator: Quentin THEURET , 2017\n" "Language-Team: French (https://www.transifex.com/oca/teams/23907/fr/)\n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #. module: web_widget_digitized_signature @@ -33,16 +33,6 @@ msgstr "Effacer" msgid "Could not display the selected image." msgstr "Impossible d'afficher l'image sélectionnée." -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_uid -msgid "Created by" -msgstr "Créé par" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_date -msgid "Created on" -msgstr "Créé le" - #. module: web_widget_digitized_signature #: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 #, python-format @@ -68,16 +58,6 @@ msgstr "Fil du courriel" msgid "Image" msgstr "Image" -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_uid -msgid "Last Updated by" -msgstr "Mis à jour par" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_date -msgid "Last Updated on" -msgstr "Mis à jour le" - #. module: web_widget_digitized_signature #: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image msgid "Signature" @@ -105,3 +85,15 @@ msgstr "La signature a été supprimée." #: model:ir.model,name:web_widget_digitized_signature.model_res_users msgid "Users" msgstr "Utilisateurs" + +#~ msgid "Created by" +#~ msgstr "Créé par" + +#~ msgid "Created on" +#~ msgstr "Créé le" + +#~ msgid "Last Updated by" +#~ msgstr "Mis à jour par" + +#~ msgid "Last Updated on" +#~ msgstr "Mis à jour le" diff --git a/web_widget_digitized_signature/i18n/hr.po b/web_widget_digitized_signature/i18n/hr.po new file mode 100644 index 00000000..52ecef5b --- /dev/null +++ b/web_widget_digitized_signature/i18n/hr.po @@ -0,0 +1,87 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +# Translators: +# Bole , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"Last-Translator: Bole , 2017\n" +"Language-Team: Croatian (https://www.transifex.com/oca/teams/23907/hr/)\n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "Korisnici" diff --git a/web_widget_digitized_signature/i18n/lt.po b/web_widget_digitized_signature/i18n/lt.po new file mode 100644 index 00000000..4e4baf9f --- /dev/null +++ b/web_widget_digitized_signature/i18n/lt.po @@ -0,0 +1,87 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +# Translators: +# Viktoras Norkus , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-25 01:58+0000\n" +"PO-Revision-Date: 2018-01-25 01:58+0000\n" +"Last-Translator: Viktoras Norkus , 2018\n" +"Language-Team: Lithuanian (https://www.transifex.com/oca/teams/23907/lt/)\n" +"Language: lt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" +"%100<10 || n%100>=20) ? 1 : 2);\n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "Vartotojai" diff --git a/web_widget_digitized_signature/i18n/nl_NL.po b/web_widget_digitized_signature/i18n/nl_NL.po new file mode 100644 index 00000000..9d2b9569 --- /dev/null +++ b/web_widget_digitized_signature/i18n/nl_NL.po @@ -0,0 +1,87 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +# Translators: +# Peter Hageman , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"Last-Translator: Peter Hageman , 2017\n" +"Language-Team: Dutch (Netherlands) (https://www.transifex.com/oca/" +"teams/23907/nl_NL/)\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "Gebruikers" diff --git a/web_widget_digitized_signature/i18n/pt_BR.po b/web_widget_digitized_signature/i18n/pt_BR.po new file mode 100644 index 00000000..a5513382 --- /dev/null +++ b/web_widget_digitized_signature/i18n/pt_BR.po @@ -0,0 +1,88 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +# Translators: +# Rodrigo de Almeida Sottomaior Macedo , 2017 +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-01-03 03:50+0000\n" +"PO-Revision-Date: 2018-01-03 03:50+0000\n" +"Last-Translator: Rodrigo de Almeida Sottomaior Macedo " +", 2017\n" +"Language-Team: Portuguese (Brazil) (https://www.transifex.com/oca/" +"teams/23907/pt_BR/)\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "Usuários" diff --git a/web_widget_digitized_signature/i18n/sl.po b/web_widget_digitized_signature/i18n/sl.po index aa8fb8de..5bb3e102 100644 --- a/web_widget_digitized_signature/i18n/sl.po +++ b/web_widget_digitized_signature/i18n/sl.po @@ -1,7 +1,7 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: # * web_widget_digitized_signature -# +# # Translators: # OCA Transbot , 2017 msgid "" @@ -12,11 +12,12 @@ msgstr "" "PO-Revision-Date: 2017-07-30 02:50+0000\n" "Last-Translator: OCA Transbot , 2017\n" "Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n" +"Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" -"Language: sl\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n" +"%100==4 ? 2 : 3);\n" #. module: web_widget_digitized_signature #. openerp-web @@ -32,16 +33,6 @@ msgstr "" msgid "Could not display the selected image." msgstr "" -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_uid -msgid "Created by" -msgstr "Ustvaril" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_create_date -msgid "Created on" -msgstr "Ustvarjeno" - #. module: web_widget_digitized_signature #: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 #, python-format @@ -67,16 +58,6 @@ msgstr "" msgid "Image" msgstr "" -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_uid -msgid "Last Updated by" -msgstr "Zadnjič posodobil" - -#. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_mail_thread_write_date -msgid "Last Updated on" -msgstr "Zadnjič posodobljeno" - #. module: web_widget_digitized_signature #: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image msgid "Signature" @@ -104,3 +85,15 @@ msgstr "" #: model:ir.model,name:web_widget_digitized_signature.model_res_users msgid "Users" msgstr "" + +#~ msgid "Created by" +#~ msgstr "Ustvaril" + +#~ msgid "Created on" +#~ msgstr "Ustvarjeno" + +#~ msgid "Last Updated by" +#~ msgstr "Zadnjič posodobil" + +#~ msgid "Last Updated on" +#~ msgstr "Zadnjič posodobljeno" diff --git a/web_widget_digitized_signature/i18n/web_widget_digitized_signature.pot b/web_widget_digitized_signature/i18n/web_widget_digitized_signature.pot new file mode 100644 index 00000000..cf175089 --- /dev/null +++ b/web_widget_digitized_signature/i18n/web_widget_digitized_signature.pot @@ -0,0 +1,82 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_widget_digitized_signature +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#, python-format +msgid "Clear" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Could not display the selected image." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#, python-format +msgid "Deletion date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#, python-format +msgid "Draw your signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_mail_thread +msgid "Email Thread" +msgstr "" + +#. module: web_widget_digitized_signature +#. openerp-web +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#, python-format +msgid "Image" +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +msgid "Signature" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#, python-format +msgid "Signature date: %s" +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#, python-format +msgid "Signature has been created." +msgstr "" + +#. module: web_widget_digitized_signature +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#, python-format +msgid "Signature has been deleted." +msgstr "" + +#. module: web_widget_digitized_signature +#: model:ir.model,name:web_widget_digitized_signature.model_res_users +msgid "Users" +msgstr "" + From f93e5310388a518c46ff94e3fc14a9e19188d780 Mon Sep 17 00:00:00 2001 From: cubells Date: Fri, 19 Apr 2019 19:25:21 +0200 Subject: [PATCH 7/7] [MIG] web_widget_digitized_signature: Migration to 11.0 --- web_widget_digitized_signature/README.rst | 82 ++-- web_widget_digitized_signature/__init__.py | 1 - .../__manifest__.py | 5 +- .../i18n/web_widget_digitized_signature.pot | 24 +- .../models/__init__.py | 1 - .../models/mail_thread.py | 1 - .../models/res_users.py | 8 +- .../readme/CONFIGURE.rst | 5 + .../readme/CONTRIBUTORS.rst | 4 + .../readme/DESCRIPTION.rst | 5 + .../readme/USAGE.rst | 3 + .../static/description/index.html | 448 ++++++++++++++++++ .../static/lib/jSignature/jSignatureCustom.js | 239 +++++----- .../static/src/js/digital_sign.js | 194 ++++---- .../static/src/xml/digital_sign.xml | 36 +- .../tests/__init__.py | 1 - .../tests/test_signature_tracking.py | 5 +- .../views/res_users_view.xml | 20 +- .../views/web_digital_sign_view.xml | 1 - 19 files changed, 811 insertions(+), 272 deletions(-) create mode 100644 web_widget_digitized_signature/readme/CONFIGURE.rst create mode 100644 web_widget_digitized_signature/readme/CONTRIBUTORS.rst create mode 100644 web_widget_digitized_signature/readme/DESCRIPTION.rst create mode 100644 web_widget_digitized_signature/readme/USAGE.rst create mode 100644 web_widget_digitized_signature/static/description/index.html diff --git a/web_widget_digitized_signature/README.rst b/web_widget_digitized_signature/README.rst index 471728f2..bca4c557 100644 --- a/web_widget_digitized_signature/README.rst +++ b/web_widget_digitized_signature/README.rst @@ -1,10 +1,29 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 - -======================= -Web Digitized Signature -======================= +============================== +Web Widget Digitized Signature +============================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github + :target: https://github.com/OCA/web/tree/11.0/web_widget_digitized_signature + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-11-0/web-11-0-web_widget_digitized_signature + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/162/11.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| This module provides a widget for binary fields that allows to digitize a signature and store it as an image. @@ -12,13 +31,19 @@ signature and store it as an image. As demonstration, it includes this widget at user level, so that we can store a signature image for each user. +**Table of contents** + +.. contents:: + :local: + Configuration ============= #. To use this module, you need to add ``widget="signature"`` to your binary field in your view. #. You can specifify signature dimensions like the following: - ```` + ```` Usage ===== @@ -27,44 +52,47 @@ Usage #. Open one of the existing users. #. You can set a digital signature for it on the field "Signature". - -.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas - :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/162/10.0 - Bug Tracker =========== -Bugs are tracked on `GitHub Issues -`_. In case of trouble, please -check there if your issue has already been reported. If you spotted it first, -help us smashing it by providing a detailed and welcomed feedback. +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. Credits ======= -Images ------- +Authors +~~~~~~~ -* Odoo Community Association: `Icon `_. +* Serpent Consulting Services Pvt. Ltd. +* Agile Business Group +* Tecnativa Contributors ------------- +~~~~~~~~~~~~ * Jay Vora -* Vicent Cubells +* Tecnativa : -Maintainer ----------- + * Vicent Cubells + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. .. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association :target: https://odoo-community.org -This module is maintained by the OCA. - OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -To contribute to this module, please visit https://odoo-community.org. +This module is part of the `OCA/web `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_widget_digitized_signature/__init__.py b/web_widget_digitized_signature/__init__.py index 96b2d789..063c0251 100644 --- a/web_widget_digitized_signature/__init__.py +++ b/web_widget_digitized_signature/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2004-2010 OpenERP SA () # Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/web_widget_digitized_signature/__manifest__.py b/web_widget_digitized_signature/__manifest__.py index 9d97eeca..5ab784ff 100644 --- a/web_widget_digitized_signature/__manifest__.py +++ b/web_widget_digitized_signature/__manifest__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2004-2010 OpenERP SA () # Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. # Copyright 2017 Tecnativa - Vicent Cubells @@ -6,11 +5,12 @@ { "name": "Web Widget Digitized Signature", - "version": "10.0.0.1.0", + "version": "11.0.0.1.0", "author": "Serpent Consulting Services Pvt. Ltd., " "Agile Business Group, " "Tecnativa, " "Odoo Community Association (OCA)", + 'website': 'https://github.com/OCA/web', "license": "AGPL-3", "category": 'Web', 'depends': [ @@ -21,7 +21,6 @@ 'views/web_digital_sign_view.xml', 'views/res_users_view.xml', ], - 'website': 'http://www.serpentcs.com', 'qweb': [ 'static/src/xml/digital_sign.xml', ], diff --git a/web_widget_digitized_signature/i18n/web_widget_digitized_signature.pot b/web_widget_digitized_signature/i18n/web_widget_digitized_signature.pot index cf175089..f784b3f8 100644 --- a/web_widget_digitized_signature/i18n/web_widget_digitized_signature.pot +++ b/web_widget_digitized_signature/i18n/web_widget_digitized_signature.pot @@ -4,8 +4,10 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.0\n" +"Project-Id-Version: Odoo Server 11.0\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-04-19 08:29+0000\n" +"PO-Revision-Date: 2019-04-19 08:29+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -15,27 +17,27 @@ msgstr "" #. module: web_widget_digitized_signature #. openerp-web -#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:7 +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:8 #, python-format msgid "Clear" msgstr "" #. module: web_widget_digitized_signature #. openerp-web -#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:99 #, python-format msgid "Could not display the selected image." msgstr "" #. module: web_widget_digitized_signature -#: code:addons/web_widget_digitized_signature/models/mail_thread.py:33 +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 #, python-format msgid "Deletion date: %s" msgstr "" #. module: web_widget_digitized_signature #. openerp-web -#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:10 +#: code:addons/web_widget_digitized_signature/static/src/xml/digital_sign.xml:11 #, python-format msgid "Draw your signature" msgstr "" @@ -47,30 +49,32 @@ msgstr "" #. module: web_widget_digitized_signature #. openerp-web -#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:81 +#: code:addons/web_widget_digitized_signature/static/src/js/digital_sign.js:99 #, python-format msgid "Image" msgstr "" #. module: web_widget_digitized_signature -#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_signature_image +#: model:ir.model.fields,field_description:web_widget_digitized_signature.field_res_users_digital_signature +#: model:ir.ui.view,arch_db:web_widget_digitized_signature.inherited_res_users_form +#: model:ir.ui.view,arch_db:web_widget_digitized_signature.inherited_res_users_preferences_form msgid "Signature" msgstr "" #. module: web_widget_digitized_signature -#: code:addons/web_widget_digitized_signature/models/mail_thread.py:30 +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:29 #, python-format msgid "Signature date: %s" msgstr "" #. module: web_widget_digitized_signature -#: code:addons/web_widget_digitized_signature/models/mail_thread.py:28 +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:27 #, python-format msgid "Signature has been created." msgstr "" #. module: web_widget_digitized_signature -#: code:addons/web_widget_digitized_signature/models/mail_thread.py:32 +#: code:addons/web_widget_digitized_signature/models/mail_thread.py:31 #, python-format msgid "Signature has been deleted." msgstr "" diff --git a/web_widget_digitized_signature/models/__init__.py b/web_widget_digitized_signature/models/__init__.py index 3f2b1438..68301ad2 100644 --- a/web_widget_digitized_signature/models/__init__.py +++ b/web_widget_digitized_signature/models/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2004-2010 OpenERP SA () # Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/web_widget_digitized_signature/models/mail_thread.py b/web_widget_digitized_signature/models/mail_thread.py index b6c3ce5d..5f4fa107 100644 --- a/web_widget_digitized_signature/models/mail_thread.py +++ b/web_widget_digitized_signature/models/mail_thread.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2004-2010 OpenERP SA () # Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. # Copyright 2017 Tecnativa - Vicent Cubells diff --git a/web_widget_digitized_signature/models/res_users.py b/web_widget_digitized_signature/models/res_users.py index 1d6fc437..e90efc99 100644 --- a/web_widget_digitized_signature/models/res_users.py +++ b/web_widget_digitized_signature/models/res_users.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2004-2010 OpenERP SA () # Copyright 2011-2015 Serpent Consulting Services Pvt. Ltd. # Copyright 2017 Tecnativa - Vicent Cubells @@ -11,17 +10,18 @@ class ResUsers(models.Model): _name = 'res.users' _inherit = ['res.users', 'mail.thread'] - signature_image = fields.Binary( + digital_signature = fields.Binary( string='Signature', + oldname="signature_image", ) @api.model def create(self, vals): res = super(ResUsers, self).create(vals) - res._track_signature(vals, 'signature_image') + res._track_signature(vals, 'digital_signature') return res @api.multi def write(self, vals): - self._track_signature(vals, 'signature_image') + self._track_signature(vals, 'digital_signature') return super(ResUsers, self).write(vals) diff --git a/web_widget_digitized_signature/readme/CONFIGURE.rst b/web_widget_digitized_signature/readme/CONFIGURE.rst new file mode 100644 index 00000000..4706fed3 --- /dev/null +++ b/web_widget_digitized_signature/readme/CONFIGURE.rst @@ -0,0 +1,5 @@ +#. To use this module, you need to add ``widget="signature"`` to your binary + field in your view. +#. You can specify signature dimensions like the following: + ```` diff --git a/web_widget_digitized_signature/readme/CONTRIBUTORS.rst b/web_widget_digitized_signature/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..888312b9 --- /dev/null +++ b/web_widget_digitized_signature/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Jay Vora +* Tecnativa : + + * Vicent Cubells diff --git a/web_widget_digitized_signature/readme/DESCRIPTION.rst b/web_widget_digitized_signature/readme/DESCRIPTION.rst new file mode 100644 index 00000000..ad46b7ee --- /dev/null +++ b/web_widget_digitized_signature/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module provides a widget for binary fields that allows to digitize a +signature and store it as an image. + +As demonstration, it includes this widget at user level, so that we can store +a signature image for each user. diff --git a/web_widget_digitized_signature/readme/USAGE.rst b/web_widget_digitized_signature/readme/USAGE.rst new file mode 100644 index 00000000..1533be61 --- /dev/null +++ b/web_widget_digitized_signature/readme/USAGE.rst @@ -0,0 +1,3 @@ +#. Go to *Settings > Users > Users*. +#. Open one of the existing users. +#. You can set a digital signature for it on the field "Signature". diff --git a/web_widget_digitized_signature/static/description/index.html b/web_widget_digitized_signature/static/description/index.html new file mode 100644 index 00000000..7d00e2c5 --- /dev/null +++ b/web_widget_digitized_signature/static/description/index.html @@ -0,0 +1,448 @@ + + + + + + +Web Widget Digitized Signature + + + +
+

Web Widget Digitized Signature

+ + +

Beta License: AGPL-3 OCA/web Translate me on Weblate Try me on Runbot

+

This module provides a widget for binary fields that allows to digitize a +signature and store it as an image.

+

As demonstration, it includes this widget at user level, so that we can store +a signature image for each user.

+

Table of contents

+ +
+

Configuration

+
    +
  1. To use this module, you need to add widget="signature" to your binary +field in your view.
  2. +
  3. You can specifify signature dimensions like the following: +<field name="signature_image" widget="signature" width="400" +height="100"/>
  4. +
+
+
+

Usage

+
    +
  1. Go to Settings > Users > Users.
  2. +
  3. Open one of the existing users.
  4. +
  5. You can set a digital signature for it on the field “Signature”.
  6. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Serpent Consulting Services Pvt. Ltd.
  • +
  • Agile Business Group
  • +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/web project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/web_widget_digitized_signature/static/lib/jSignature/jSignatureCustom.js b/web_widget_digitized_signature/static/lib/jSignature/jSignatureCustom.js index bb99cac8..bf3d7359 100644 --- a/web_widget_digitized_signature/static/lib/jSignature/jSignatureCustom.js +++ b/web_widget_digitized_signature/static/lib/jSignature/jSignatureCustom.js @@ -1,10 +1,11 @@ -/** @preserve +/** @preserve jSignature v2 "${buildDate}" "${commitID}" Copyright (c) 2012 Willow Systems Corp http://willow-systems.com Copyright (c) 2010 Brinley Ang http://www.unbolt.net -MIT License +MIT License */ + ;(function() { var apinamespace = 'jSignature' @@ -25,7 +26,7 @@ var KickTimerClass = function(time, callback) { timer = setTimeout( callback , time - ) + ) } this.clear = function() { clearTimeout(timer) @@ -35,7 +36,7 @@ var KickTimerClass = function(time, callback) { var PubSubClass = function(context){ 'use strict' - /* @preserve + /* @preserve ----------------------------------------------------------------------------------------------- JavaScript PubSub library 2012 (c) Willow Systems Corp (www.willow-systems.com) @@ -68,7 +69,7 @@ var PubSubClass = function(context){ for (i = 0, l = currentTopic.length; i < l; i++) { pair = currentTopic[i] // this is a [function, once_flag] array - fn = pair[0] + fn = pair[0] if (pair[1] /* 'run once' flag set */){ pair[0] = function(){} toremove.push(i) @@ -81,13 +82,13 @@ var PubSubClass = function(context){ } } /** - * Allows listener code to subscribe to channel and be called when data is available + * Allows listener code to subscribe to channel and be called when data is available * @public * @function * @param topic {String} Name of the channel on which to voice this event * @param callback {Function} Executable (function pointer) that will be ran when event is voiced on this channel. * @param once {Boolean} (optional. False by default) Flag indicating if the function is to be triggered only once. - * @returns {Object} A token object that cen be used for unsubscribing. + * @returns {Object} A token object that cen be used for unsubscribing. */ this.subscribe = function(topic, callback, once) { 'use strict' @@ -102,15 +103,15 @@ var PubSubClass = function(context){ }; }; /** - * Allows listener code to unsubscribe from a channel + * Allows listener code to unsubscribe from a channel * @public * @function - * @param token {Object} A token object that was returned by `subscribe` method + * @param token {Object} A token object that was returned by `subscribe` method */ this.unsubscribe = function(token) { if (this.topics[token.topic]) { var currentTopic = this.topics[token.topic] - + for (var i = 0, l = currentTopic.length; i < l; i++) { if (currentTopic[i][0] === token.callback) { currentTopic.splice(i, 1) @@ -127,7 +128,7 @@ function getColors($e){ , frontcolor = $e.css('color') , backcolor , e = $e[0] - + var toOfDOM = false while(e && !backcolor && !toOfDOM){ try{ @@ -192,18 +193,18 @@ function getColors($e){ // backcolorcomponents = {'r':0,'g':0,'b':0} // } } - + // Deriving Decor color - // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill. - - var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'} + // THis is LAZY!!!! Better way would be to use HSL and adjust luminocity. However, that could be an overkill. + + var toRGBfn = function(o){return 'rgb(' + [o.r, o.g, o.b].join(', ') + ')'} , decorcolorcomponents , frontcolorbrightness , adjusted - + if (frontcolorcomponents && backcolorcomponents){ var backcolorbrightness = Math.max.apply(null, [frontcolorcomponents.r, frontcolorcomponents.g, frontcolorcomponents.b]) - + frontcolorbrightness = Math.max.apply(null, [backcolorcomponents.r, backcolorcomponents.g, backcolorcomponents.b]) adjusted = Math.round(frontcolorbrightness + (-1 * (frontcolorbrightness - backcolorbrightness) * 0.75)) // "dimming" the difference between pen and back. decorcolorcomponents = {'r':adjusted,'g':adjusted,'b':adjusted} // always shade of gray @@ -231,7 +232,7 @@ function Vector(x,y){ this.x = x this.y = y this.reverse = function(){ - return new this.constructor( + return new this.constructor( this.x * -1 , this.y * -1 ) @@ -243,7 +244,7 @@ function Vector(x,y){ } return this._length } - + var polarity = function (e){ return Math.round(e / Math.abs(e)) } @@ -267,12 +268,12 @@ function Vector(x,y){ } return this } - + /** * Calculates the angle between 'this' vector and another. * @public * @function - * @returns {Number} The angle between the two vectors as measured in PI. + * @returns {Number} The angle between the two vectors as measured in PI. */ this.angleTo = function(vectorB) { var divisor = this.getLength() * vectorB.getLength() @@ -283,8 +284,8 @@ function Vector(x,y){ // because of it, the core of the formula can, on occasion, have values // over 1.0 and below -1.0. return Math.acos( - Math.min( - Math.max( + Math.min( + Math.max( ( this.x * vectorB.x + this.y * vectorB.y ) / divisor , -1.0 ) @@ -298,7 +299,7 @@ function Vector(x,y){ function Point(x,y){ this.x = x this.y = y - + this.getVectorToCoordinates = function (x, y) { return new Vector(x - this.x, y - this.y) } @@ -316,17 +317,17 @@ function Point(x,y){ /* * About data structure: * We don't store / deal with "pictures" this signature capture code captures "vectors" - * + * * We don't store bitmaps. We store "strokes" as arrays of arrays. (Actually, arrays of objects containing arrays of coordinates. - * + * * Stroke = mousedown + mousemoved * n (+ mouseup but we don't record that as that was the "end / lack of movement" indicator) - * + * * Vectors = not classical vectors where numbers indicated shift relative last position. Our vectors are actually coordinates against top left of canvas. - * we could calc the classical vectors, but keeping the the actual coordinates allows us (through Math.max / min) + * we could calc the classical vectors, but keeping the the actual coordinates allows us (through Math.max / min) * to calc the size of resulting drawing very quickly. If we want classical vectors later, we can always get them in backend code. - * + * * So, the data structure: - * + * * var data = [ * { // stroke starts * x : [101, 98, 57, 43] // x points @@ -341,9 +342,9 @@ function Point(x,y){ * , y : [151] // y points * } // stroke ends * ] - * + * * we don't care or store stroke width (it's canvas-size-relative), color, shadow values. These can be added / changed on whim post-capture. - * + * */ function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStrokeFn){ this.data = storageObject // we expect this to be an instance of Array @@ -354,7 +355,7 @@ function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStr var numofstrokes = storageObject.length , stroke , numofpoints - + for (var i = 0; i < numofstrokes; i++){ stroke = storageObject[i] numofpoints = stroke.x.length @@ -367,13 +368,13 @@ function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStr } this.changed = function(){} - + this.startStrokeFn = startStrokeFn this.addToStrokeFn = addToStrokeFn this.endStrokeFn = endStrokeFn this.inStroke = false - + this._lastPoint = null this._stroke = null this.startStroke = function(point){ @@ -383,7 +384,7 @@ function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStr this._lastPoint = point this.inStroke = true // 'this' does not work same inside setTimeout( - var stroke = this._stroke + var stroke = this._stroke , fn = this.startStrokeFn , context = this.context setTimeout( @@ -402,8 +403,8 @@ function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStr // when clustering of these is too tight, it produces noise on the line, which, because of smoothing, makes lines too curvy. // maybe, later, we can expose this as a configurable setting of some sort. this.addToStroke = function(point){ - if (this.inStroke && - typeof(point.x) === "number" && + if (this.inStroke && + typeof(point.x) === "number" && typeof(point.y) === "number" && // calculates absolute shift in diagonal pixels away from original point (Math.abs(point.x - this._lastPoint.x) + Math.abs(point.y - this._lastPoint.y)) > 4 @@ -412,7 +413,7 @@ function DataEngine(storageObject, context, startStrokeFn, addToStrokeFn, endStr this._stroke.x.push(point.x) this._stroke.y.push(point.y) this._lastPoint = point - + var stroke = this._stroke , fn = this.addToStrokeFn , context = this.context @@ -478,7 +479,7 @@ var basicDot = function(ctx, x, y, size){ // Because we are funky this way, here we draw TWO curves. // 1. POSSIBLY "this line" - spanning from point right before us, to this latest point. // 2. POSSIBLY "prior curve" - spanning from "latest point" to the one before it. - + // Why you ask? // long lines (ones with many pixels between them) do not look good when they are part of a large curvy stroke. // You know, the jaggedy crocodile spine instead of a pretty, smooth curve. Yuck! @@ -486,23 +487,23 @@ var basicDot = function(ctx, x, y, size){ // To approximate a very nice curve we need to know the direction of line before and after. // Hence, on long lines we actually wait for another point beyond it to come back from // mousemoved before we draw this curve. - - // So for "prior curve" to be calc'ed we need 4 points + + // So for "prior curve" to be calc'ed we need 4 points // A, B, C, D (we are on D now, A is 3 points in the past.) // and 3 lines: - // pre-line (from points A to B), - // this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.) + // pre-line (from points A to B), + // this line (from points B to C), (we call it "this" because if it was not yet, it's the only one we can draw for sure.) // post-line (from points C to D) (even through D point is 'current' we don't know how we can draw it yet) // // Well, actually, we don't need to *know* the point A, just the vector A->B var Cpoint = new Point(stroke.x[positionInStroke-1], stroke.y[positionInStroke-1]) , Dpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) , CDvector = Cpoint.getVectorToPoint(Dpoint) - + // Again, we have a chance here to draw TWO things: - // BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and + // BC Curve (only if it's long, because if it was short, it was drawn by previous callback) and // CD Line (only if it's short) - + // So, let's start with BC curve. // if there is only 2 points in stroke array, we don't have "history" long enough to have point B, let alone point A. // Falling through to drawing line CD is proper, as that's the only line we have points for. @@ -530,7 +531,7 @@ var basicDot = function(ctx, x, y, size){ , CCP2vector = (new Vector(BCvector.x + CDvector.x, BCvector.y + CDvector.y)).reverse().resizeTo( Math.max(minlenfraction, BCDangle) * maxlen ) - + basicCurve( this.canvasContext , Bpoint.x @@ -558,23 +559,23 @@ var basicDot = function(ctx, x, y, size){ // this = jSignatureClass instance // Here we tidy up things left unfinished in last strokeAddCallback run. - + // What's POTENTIALLY left unfinished there is the curve between the last points // in the stroke, if the len of that line is more than lineCurveThreshold // If the last line was shorter than lineCurveThreshold, it was drawn there, and there // is nothing for us here to do. - // We can also be called when there is only one point in the stroke (meaning, the + // We can also be called when there is only one point in the stroke (meaning, the // stroke was just a dot), in which case, again, there is nothing for us to do. - - // So for "this curve" to be calc'ed we need 3 points + + // So for "this curve" to be calc'ed we need 3 points // A, B, C // and 2 lines: - // pre-line (from points A to B), - // this line (from points B to C) + // pre-line (from points A to B), + // this line (from points B to C) // Well, actually, we don't need to *know* the point A, just the vector A->B // so, we really need points B, C and AB vector. var positionInStroke = stroke.x.length - 1 - + if (positionInStroke > 0){ // there are at least 2 points in the stroke.we are in business. var Cpoint = new Point(stroke.x[positionInStroke], stroke.y[positionInStroke]) @@ -649,7 +650,7 @@ var getDataStats = function(){ function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, settingsWidth, apinamespace, globalEvents){ 'use strict' if ( settingsWidth === 'ratio' || settingsWidth.split('')[settingsWidth.length - 1] === '%' ) { - + this.eventTokens[apinamespace + '.parentresized'] = globalEvents.subscribe( apinamespace + '.parentresized' , (function(eventTokens, $parent, originalParentWidth, sizeRatio){ @@ -660,9 +661,9 @@ function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, setting var w = $parent.width() if (w !== originalParentWidth) { - + // UNsubscribing this particular instance of signature pad only. - // there is a separate `eventTokens` per each instance of signature pad + // there is a separate `eventTokens` per each instance of signature pad for (var key in eventTokens){ if (eventTokens.hasOwnProperty(key)) { globalEvents.unsubscribe(eventTokens[key]) @@ -677,21 +678,21 @@ function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, setting delete jSignatureInstance[key] } } - + // scale data to new signature pad size settings.data = (function(data, scale){ var newData = [] var o, i, l, j, m, stroke for ( i = 0, l = data.length; i < l; i++) { stroke = data[i] - + o = {'x':[],'y':[]} - + for ( j = 0, m = stroke.x.length; j < m; j++) { o.x.push(stroke.x[j] * scale) o.y.push(stroke.y[j] * scale) } - + newData.push(o) } return newData @@ -699,7 +700,7 @@ function conditionallyLinkCanvasResizeToWindowResize(jSignatureInstance, setting settings.data , w * 1.0 / originalParentWidth ) - + $parent[apinamespace](settings) } } @@ -769,14 +770,14 @@ function jSignatureClass(parent, options, instanceExtensions) { // Most of our exposed API will be looking for this: $canvas.data(apinamespace + '.this', this) - - + + settings.lineWidth = (function(defaultLineWidth, canvasWidth){ if (!defaultLineWidth){ return Math.max( Math.round(canvasWidth / 400) /*+1 pixel for every extra 300px of width.*/ , 2 /* minimum line width */ - ) + ) } else { return defaultLineWidth } @@ -830,7 +831,7 @@ function jSignatureClass(parent, options, instanceExtensions) { this.drawStartHandler = function(e) { e.preventDefault() // for performance we cache the offsets - // we recalc these only at the beginning the stroke + // we recalc these only at the beginning the stroke setStartValues() jSignatureInstance.dataEngine.startStroke( getPointFromEvent(e) ) timer.kick() @@ -839,7 +840,7 @@ function jSignatureClass(parent, options, instanceExtensions) { e.preventDefault() if (!jSignatureInstance.dataEngine.inStroke){ return - } + } jSignatureInstance.dataEngine.addToStroke( getPointFromEvent(e) ) timer.kick() } @@ -866,7 +867,7 @@ function jSignatureClass(parent, options, instanceExtensions) { canvas.onmousemove = undef this.fatFingerCompensation = ( - settings.minFatFingerCompensation && + settings.minFatFingerCompensation && settings.lineWidth * -3 > settings.minFatFingerCompensation ) ? settings.lineWidth * -3 : settings.minFatFingerCompensation @@ -888,7 +889,7 @@ function jSignatureClass(parent, options, instanceExtensions) { canvas.onmousemove = drawMoveHandler } } - }).call( + }).call( this , movementHandlers.drawEndHandler , movementHandlers.drawStartHandler @@ -918,7 +919,7 @@ function jSignatureClass(parent, options, instanceExtensions) { , settings.width.toString(10) , apinamespace, globalEvents ) - + // end of event handlers. // =============================== @@ -944,7 +945,7 @@ jSignatureClass.prototype.resetCanvas = function(data){ , cw = canvas.width , ch = canvas.height - + // preparing colors, drawing area ctx.clearRect(0, 0, cw + 30, ch + 30) @@ -952,13 +953,13 @@ jSignatureClass.prototype.resetCanvas = function(data){ ctx.shadowColor = ctx.fillStyle = settings['background-color'] if (isCanvasEmulator){ // FLashCanvas fills with Black by default, covering up the parent div's background - // hence we refill + // hence we refill ctx.fillRect(0,0,cw + 30, ch + 30) } ctx.lineWidth = Math.ceil(parseInt(settings.lineWidth, 10)) ctx.lineCap = ctx.lineJoin = "round" - + // signature line ctx.strokeStyle = settings['decor-color'] ctx.shadowOffsetX = 0 @@ -971,13 +972,13 @@ jSignatureClass.prototype.resetCanvas = function(data){ ctx.shadowColor = ctx.strokeStyle ctx.shadowOffsetX = ctx.lineWidth * 0.5 ctx.shadowOffsetY = ctx.lineWidth * -0.6 - ctx.shadowBlur = 0 + ctx.shadowBlur = 0 } - + // setting up new dataEngine if (!data) { data = [] } - + var dataEngine = this.dataEngine = new DataEngine( data , this @@ -996,7 +997,7 @@ jSignatureClass.prototype.resetCanvas = function(data){ 'use strict' return function() { events.publish(apinamespace+'.change') - target.trigger('change') + target.trigger('change') } })(this.$parent, this.events, apinamespace) // let's trigger change on all data reloads @@ -1026,9 +1027,9 @@ function initializeCanvasEmulator(canvas){ if (FC) { canvas = FC.initElement(canvas) - + var zoom = 1 - // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom. + // FlashCanvas uses flash which has this annoying habit of NOT scaling with page zoom. // It matches pixel-to-pixel to screen instead. // Since we are targeting ONLY IE 7, 8 with FlashCanvas, we will test the zoom only the IE8, IE7 way if (window && window.screen && window.screen.deviceXDPI && window.screen.logicalXDPI){ @@ -1105,7 +1106,7 @@ jSignatureClass.prototype.initializeCanvas = function(settings) { canvas.width = $canvas.width() canvas.height = $canvas.height() - + // Special case Sizing code this.isCanvasEmulator = initializeCanvasEmulator(canvas) @@ -1124,7 +1125,7 @@ jSignatureClass.prototype.initializeCanvas = function(settings) { var GlobalJSignatureObjectInitializer = function(window){ var globalEvents = new PubSubClass() - + // common "window resized" event listener. // jSignature instances will subscribe to this chanel. // to resize themselves when needed. @@ -1144,7 +1145,7 @@ var GlobalJSignatureObjectInitializer = function(window){ if (resizetimer) { clearTimeout(resizetimer) } - resizetimer = setTimeout( + resizetimer = setTimeout( runner , 500 ) @@ -1160,13 +1161,13 @@ var GlobalJSignatureObjectInitializer = function(window){ })(globalEvents, apinamespace, $, window) var jSignatureInstanceExtensions = { - + 'exampleExtension':function(extensionName){ // we are called very early in instance's life. - // right after the settings are resolved and - // jSignatureInstance.events is created + // right after the settings are resolved and + // jSignatureInstance.events is created // and right before first ("jSignature.initializing") event is called. - // You don't really need to manupilate + // You don't really need to manupilate // jSignatureInstance directly, just attach // a bunch of events to jSignatureInstance.events // (look at the source of jSignatureClass to see when these fire) @@ -1201,7 +1202,7 @@ var GlobalJSignatureObjectInitializer = function(window){ } ) } - + } var exportplugins = { @@ -1210,12 +1211,12 @@ var GlobalJSignatureObjectInitializer = function(window){ , 'image':function(data){ /*this = canvas elem */ var imagestring = this.toDataURL() - - if (typeof imagestring === 'string' && - imagestring.length > 4 && + + if (typeof imagestring === 'string' && + imagestring.length > 4 && imagestring.slice(0,5) === 'data:' && imagestring.indexOf(',') !== -1){ - + var splitterpos = imagestring.indexOf(',') return [ @@ -1230,9 +1231,9 @@ var GlobalJSignatureObjectInitializer = function(window){ // will be part of "importplugins" function _renderImageOnCanvas( data, formattype, rerendercallable ) { 'use strict' - // #1. Do NOT rely on this. No worky on IE + // #1. Do NOT rely on this. No worky on IE // (url max len + lack of base64 decoder + possibly other issues) - // #2. This does NOT affect what is captured as "signature" as far as vector data is + // #2. This does NOT affect what is captured as "signature" as far as vector data is // concerned. This is treated same as "signature line" - i.e. completely ignored // the only time you see imported image data exported is if you export as image. @@ -1240,20 +1241,32 @@ var GlobalJSignatureObjectInitializer = function(window){ // because importing image does absolutely nothing to the underlying vector data storage // This could be a way to "import" old signatures stored as images // This could also be a way to import extra decor into signature area. - - var img = new Image() - // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div. - , c = this - - img.onload = function() { - var ctx = c.getContext("2d").drawImage( - img, 0, 0 - , ( img.width < c.width) ? img.width : c.width - , ( img.height < c.height) ? img.height : c.height - ) - } - img.src = 'data:' + formattype + ',' + data +// var img = new Image() +// // this = Canvas DOM elem. Not jQuery object. Not Canvas's parent div. +// , c = this +// +// img.onload = function() { +// var ctx = c.getContext("2d").drawImage( +// img, 0, 0 +// , ( img.width < c.width) ? img.width : c.width +// , ( img.height < c.height) ? img.height : c.height +// ) +// } +// +// img.src = 'data:' + formattype + ',' + data + + var c = new Image, + e = this; + c.onload = function() { + var a = e.getContext("2d"), + b = a.shadowColor; + a.shadowColor = "transparent"; + a.drawImage(c, 0, 0, c.width < e.width ? c.width : e.width, c.height < + e.height ? c.height : e.height); + a.shadowColor = b + }; + c.src = "data:" + formattype + "," + data } var importplugins = { @@ -1281,7 +1294,7 @@ var GlobalJSignatureObjectInitializer = function(window){ if (formattype === undef && typeof data === 'string' && data.substr(0,5) === 'data:') { formattype = data.slice(5).split(',')[0] // 5 chars of "data:" + mimetype len + 1 "," char = all skipped. - data = data.slice(6 + formattype.length) + data = data.slice(6 + formattype.length) if (formattype === data) return } @@ -1294,7 +1307,7 @@ var GlobalJSignatureObjectInitializer = function(window){ $canvas[0] , data , formattype - , (function(jSignatureInstance){ + , (function(jSignatureInstance){ return function(){ return jSignatureInstance.resetCanvas.apply(jSignatureInstance, arguments) } })($canvas.data(apinamespace+'.this')) ) @@ -1307,7 +1320,7 @@ var GlobalJSignatureObjectInitializer = function(window){ var topOfDOM = false e = e.parentNode while (e && !topOfDOM){ - topOfDOM = $(e).find(".oe_form") + topOfDOM = $(e).find(".o_form_view") e = e.parentNode } return !topOfDOM @@ -1319,7 +1332,7 @@ var GlobalJSignatureObjectInitializer = function(window){ 'init' : function( options ) { return this.each( function() { if (!elementIsOrphan(this)) { - new jSignatureClass(this, options, jSignatureInstanceExtensions) + new jSignatureClass(this, options, jSignatureInstanceExtensions) } }) } @@ -1353,7 +1366,7 @@ var GlobalJSignatureObjectInitializer = function(window){ , 'getData' : function( formattype ) { var undef, $canvas=this.find('canvas.'+apinamespace).add(this.filter('canvas.'+apinamespace)) if (formattype === undef) formattype = 'default' - if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){ + if ($canvas.length !== 0 && exportplugins.hasOwnProperty(formattype)){ return exportplugins[formattype].call( $canvas.get(0) // canvas dom elem , $canvas.data(apinamespace+'.data') // raw signature data as array of objects of arrays @@ -1373,7 +1386,7 @@ var GlobalJSignatureObjectInitializer = function(window){ .data(apinamespace+'.this').events } } // end of methods declaration. - + $.fn[apinamespace] = function(method) { 'use strict' if ( !method || typeof method === 'object' ) { diff --git a/web_widget_digitized_signature/static/src/js/digital_sign.js b/web_widget_digitized_signature/static/src/js/digital_sign.js index 468a644a..842f89a8 100644 --- a/web_widget_digitized_signature/static/src/js/digital_sign.js +++ b/web_widget_digitized_signature/static/src/js/digital_sign.js @@ -2,121 +2,155 @@ odoo.define('web_widget_digitized_signature.web_digital_sign', function(require) "use strict"; var core = require('web.core'); - var FormView = require('web.FormView'); + var BasicFields= require('web.basic_fields'); + var FormController = require('web.FormController'); + var Registry = require('web.field_registry'); var utils = require('web.utils'); var session = require('web.session'); - var Model = require('web.Model'); + var field_utils = require('web.field_utils'); var _t = core._t; var QWeb = core.qweb; - var FieldSignature = core.form_widget_registry.map.image.extend({ + var FieldSignature = BasicFields.FieldBinaryImage.extend({ template: 'FieldSignature', + events: _.extend({}, BasicFields.FieldBinaryImage.prototype.events, { + 'click .save_sign': '_on_save_sign', + 'click #sign_clean': '_on_clear_sign' + }), + jsLibs: ['/web_widget_digitized_signature/static/lib/jSignature/jSignatureCustom.js'], placeholder: "/web/static/src/img/placeholder.png", - initialize_content: function() { - var self = this; - this.$el.find('> img').remove(); - this.$el.find('.signature > canvas').remove(); - var sign_options = {'decor-color' : '#D1D0CE', 'color': '#000', 'background-color': '#fff','height':'150','width':'550'}; - this.$el.find(".signature").jSignature("init",sign_options); - this.$el.find(".signature").attr({"tabindex": "0",'height':"100"}); - this.empty_sign = this.$el.find(".signature").jSignature("getData",'image'); - this.$el.find('#sign_clean').click(this.on_clear_sign); - this.$el.find('.save_sign').click(this.on_save_sign); + init: function() { + this._super.apply(this, arguments); + this.$('> img').remove(); + this.$('.signature > canvas').remove(); + this.sign_options = { + 'decor-color': '#D1D0CE', + 'color': '#000', + 'background-color': '#fff', + 'height': '150', + 'width': '550' + }; + this.empty_sign = []; }, - on_clear_sign: function() { + start: function() { var self = this; - this.$el.find(".signature > canvas").remove(); - this.$el.find('> img').remove(); - this.$el.find(".signature").attr("tabindex", "0"); - var sign_options = {'decor-color' : '#D1D0CE', 'color': '#000', 'background-color': '#fff','height':'150','width':'550','clear': true}; - this.$el.find(".signature").jSignature(sign_options); - this.$el.find(".signature").focus(); - self.set('value', false); + this.$(".signature").jSignature("init", this.sign_options); + this.$(".signature").attr({ + "tabindex": "0", + 'height': "100" + }); + this.empty_sign = this.$(".signature").jSignature("getData", 'image'); + self._render(); + }, + _on_clear_sign: function() { + this.$(".signature > canvas").remove(); + this.$('> img').remove(); + this.$(".signature").attr("tabindex", "0"); + var sign_options = { + 'decor-color': '#D1D0CE', + 'color': '#000', + 'background-color': '#fff', + 'height': '150', + 'width': '550', + 'clear': true + }; + this.$(".signature").jSignature(sign_options); + this.$(".signature").focus(); + this._setValue(false); }, - on_save_sign: function(value_) { + _on_save_sign: function(value_) { var self = this; - this.$el.find('> img').remove(); - var signature = self.$el.find(".signature").jSignature("getData",'image'); - var is_empty = signature - ? self.empty_sign[1] === signature[1] - : false; - if (! is_empty && typeof signature !== "undefined" && signature[1]) { - self.set('value',signature[1]); + this.$('> img').remove(); + var signature = this.$(".signature").jSignature("getData", 'image'); + var is_empty = signature ? + self.empty_sign[1] === signature[1] : + false; + if (!is_empty && typeof signature !== "undefined" && signature[1]) { + this._setValue(signature[1]); } }, - render_value: function() { + _render: function() { var self = this; var url = this.placeholder; - if (this.get('value') && !utils.is_bin_size(this.get('value'))) { - url = 'data:image/png;base64,' + this.get('value'); - } else if (this.get('value')) { - url = this.session.url('/web/binary/image', { - model: this.view.dataset.model, - id: JSON.stringify(this.view.datarecord.id || null), - field: this.options.preview_image - ? this.options.preview_image - : this.name, - t: new Date().getTime() + if (this.value && !utils.is_bin_size(this.value)) { + url = 'data:image/png;base64,' + this.value; + } else if (this.value) { + url = session.url('/web/image', { + model: this.model, + id: JSON.stringify(this.res_id), + field: this.nodeOptions.preview_image || this.name, + unique: field_utils.format.datetime(this.recordData.__last_update).replace(/[^0-9]/g, ''), }); } else { url = this.placeholder; } - if (this.view.get("actual_mode") === 'view') { - var $img = $(QWeb.render("FieldBinaryImage-extend", { widget: this, url: url })); - this.$el.find('> img').remove(); - this.$el.find(".signature").hide(); + if (this.mode === "readonly") { + var $img = $(QWeb.render("FieldBinaryImage-img", { + widget: self, + url: url + })); + this.$('> img').remove(); + this.$(".signature").hide(); this.$el.prepend($img); - $img.load(function() { - if (! self.options.size) { - return; - } - $img.css("max-width", "" + self.options.size[0] + "px"); - $img.css("max-height", "" + self.options.size[1] + "px"); - $img.css("margin-left", "" + (self.options.size[0] - $img.width()) / 2 + "px"); - $img.css("margin-top", "" + (self.options.size[1] - $img.height()) / 2 + "px"); - }); $img.on('error', function() { + self.on_clear(); $img.attr('src', self.placeholder); self.do_warn(_t("Image"), _t("Could not display the selected image.")); }); - } else if (this.view.get("actual_mode") === 'edit') { - this.$el.find('> img').remove(); - if (this.get('value')) { - var field_name = this.options.preview_image - ? this.options.preview_image - : this.name; - new Model(this.view.dataset.model).call("read", [this.view.datarecord.id, [field_name]]).done(function(data) { + } else if (this.mode === "edit") { + this.$('> img').remove(); + if (this.value) { + var field_name = this.nodeOptions.preview_image ? + this.nodeOptions.preview_image : + this.name; + self._rpc({ + model: this.model, + method: 'read', + args: [this.res_id, [field_name]] + }).done(function(data) { if (data) { - var field_desc = _.values(_.pick(data, field_name)); - self.$el.find(".signature").jSignature("reset"); - self.$el.find(".signature").jSignature("setData",'data:image/png;base64,'+field_desc[0]); + var field_desc = _.values(_.pick(data[0], field_name)); + self.$(".signature").jSignature("clear"); + self.$(".signature").jSignature("setData", 'data:image/png;base64,' + field_desc[0]); } }); } else { - this.$el.find('> img').remove(); - this.$el.find('.signature > canvas').remove(); - var sign_options = {'decor-color' : '#D1D0CE', 'color': '#000','background-color': '#fff','height':'150','width':'550'}; - this.$el.find(".signature").jSignature("init",sign_options); + this.$('> img').remove(); + this.$('.signature > canvas').remove(); + var sign_options = { + 'decor-color': '#D1D0CE', + 'color': '#000', + 'background-color': '#fff', + 'height': '150', + 'width': '550' + }; + this.$(".signature").jSignature("init", sign_options); + } + } else if (this.mode === 'create') { + this.$('> img').remove(); + this.$('> canvas').remove(); + if (!this.value) { + this.$(".signature").empty().jSignature("init", { + 'decor-color': '#D1D0CE', + 'color': '#000', + 'background-color': '#fff', + 'height': '150', + 'width': '550' + }); } - } else if (this.view.get("actual_mode") === 'create') { - this.$el.find('> img').remove(); - this.$el.find('> canvas').remove(); - if (!this.get('value')) { - this.$el.find(".signature").empty().jSignature("init",{'decor-color' : '#D1D0CE', 'color': '#000','background-color': '#fff','height':'150','width':'550'}); - } - } + } } }); - core.form_widget_registry.add('signature', FieldSignature); - - FormView.include({ - save: function() { - this.$el.find('.save_sign').click(); + FormController.include({ + saveRecord: function() { + this.$('.save_sign').click(); return this._super.apply(this, arguments); } }); -}); + Registry.add('signature', FieldSignature); + +}); diff --git a/web_widget_digitized_signature/static/src/xml/digital_sign.xml b/web_widget_digitized_signature/static/src/xml/digital_sign.xml index df4e8e22..e722a2cc 100644 --- a/web_widget_digitized_signature/static/src/xml/digital_sign.xml +++ b/web_widget_digitized_signature/static/src/xml/digital_sign.xml @@ -1,24 +1,22 @@ - + diff --git a/web_widget_digitized_signature/tests/__init__.py b/web_widget_digitized_signature/tests/__init__.py index 3dc777d5..9aa8c1e2 100644 --- a/web_widget_digitized_signature/tests/__init__.py +++ b/web_widget_digitized_signature/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import test_signature_tracking diff --git a/web_widget_digitized_signature/tests/test_signature_tracking.py b/web_widget_digitized_signature/tests/test_signature_tracking.py index 98d04421..1ca800dd 100644 --- a/web_widget_digitized_signature/tests/test_signature_tracking.py +++ b/web_widget_digitized_signature/tests/test_signature_tracking.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2017 Tecnativa - Pedro M. Baeza # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). @@ -25,14 +24,14 @@ class TestSignatureTracking(common.SavepointCase): direct chatter""" prev_attachment_num = self.attachment_obj.search_count([]) prev_messages = self.message_obj.search([]) - self.user.signature_image = self.image + self.user.digital_signature = self.image current_attachment_num = self.attachment_obj.search_count([]) self.assertEqual(current_attachment_num - prev_attachment_num, 1) current_messages = self.message_obj.search([]) message = current_messages - prev_messages self.assertIn('Signature has been created.', message.body) prev_messages = current_messages - self.user.signature_image = False + self.user.digital_signature = False current_messages = self.message_obj.search([]) message = current_messages - prev_messages self.assertIn('Signature has been deleted.', message.body) diff --git a/web_widget_digitized_signature/views/res_users_view.xml b/web_widget_digitized_signature/views/res_users_view.xml index 3971c209..49934e08 100644 --- a/web_widget_digitized_signature/views/res_users_view.xml +++ b/web_widget_digitized_signature/views/res_users_view.xml @@ -4,22 +4,26 @@ inherited.res.users.form res.users - + - - - res.users.preferences.form + + inherited.res.users.preferences.form res.users - + - diff --git a/web_widget_digitized_signature/views/web_digital_sign_view.xml b/web_widget_digitized_signature/views/web_digital_sign_view.xml index 4331c08c..9c63731d 100644 --- a/web_widget_digitized_signature/views/web_digital_sign_view.xml +++ b/web_widget_digitized_signature/views/web_digital_sign_view.xml @@ -3,7 +3,6 @@