Browse Source

firma digital

Your Name 4 years ago
commit
07c01a826b

+ 24 - 0
__init__.py

@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
+#    Copyright (C) 2011-2015 Serpent Consulting Services Pvt. Ltd. (<http://www.serpentcs.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import users
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

BIN
__init__.pyc


+ 70 - 0
__openerp__.py

@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
+#    Copyright (C) 2011-2015 Serpent Consulting Services Pvt. Ltd. (<http://www.serpentcs.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name": "Web Digital Signature",
+    "version": "8.0.0.0.1",
+    "author": "Serpent Consulting Services Pvt. Ltd.",
+    "category": 'Tools',
+    'complexity': "easy",
+     'license': 'AGPL-3',
+    'depends': ['web'],
+    'description': '''
+     This module provides the functionality to store digital signature
+     for a record.
+        -> This  module is helpfull to make your business process a little
+           bit more faster & makes it more user friendly by providing you
+           digital signature functionality on your documents.
+        -> It is touch screen enable so user can add signature with touch
+           devices.
+        -> Digital signature can be very usefull for documents such as
+           sale orders, purchase orders, invoices, payslips, procurement
+           receipts, etc.
+        The example can be seen into the User's form view where we have
+        added a test field under signature.
+    ''',
+    'summary': '''
+     This module provides the functionality to store digital signature
+     for a record.
+        -> This  module is helpfull to make your business process a little
+           bit more faster & makes it more user friendly by providing you
+           digital signature functionality on your documents.
+        -> It is touch screen enable so user can add signature with touch
+           devices.
+        -> Digital signature can be very usefull for documents such as
+           sale orders, purchase orders, invoices, payslips, procurement
+           receipts, etc.
+        The example can be seen into the User's form view where we have
+        added a test field under signature.
+    ''',
+    'images': ['static/description/Digital_Signature.jpg'],
+    'data': [
+        'views/we_digital_sign_view.xml',
+        'users_view.xml'
+    ],
+    'website': 'http://www.serpentcs.com',
+    'qweb': ['static/src/xml/digital_sign.xml'],
+    'installable': True,
+    'auto_install': False,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

BIN
static/description/2.png


BIN
static/description/3.png


BIN
static/description/Digital_Signature.jpg


BIN
static/description/fb.jpg


BIN
static/description/icon.png


BIN
static/description/in.jpg


+ 248 - 0
static/description/index.html

@@ -0,0 +1,248 @@
+<section class="oe_container">
+    <div class="oe_row">
+        <div class="text-justify" style="background:#dcdcdc;padding-top:2px;padding-bottom:2px">
+            <h2 class="oe_slogan" style="color:#875A7B;font-family:times new roman;margin-top:20px;margin-bottom:20px;">
+                <span class="label" style="background:#875A7B;">
+                    Digital Signature
+                </span>
+            </h2>
+            <hr width="60%" style="border-style:solid;border-top-width:2px;" />
+            <h3 class="oe_slogan" style="font-family:times new roman;">This module provides the functionality to store digital signature for a record</h3>
+            <table class="text-justify" style="margin-left: 10px;margin-right: 10px;margin-bottom: 10px;">
+                <tbody>
+                    <tr>
+                        <td>
+                            <span class="fa fa-hand-o-right fa-2x" style="padding-bottom:25px;padding-right:10px;color:#875A7B;"/>
+                        </td>
+                        <td class="oe_mt8" style="font-size: 18px;font-family:times new roman;">
+                            This  module is helpful to make your business process a little bit more faster & makes it more user friendly by providing you digital signature functionality on your documents.
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            <span class="fa fa-hand-o-right fa-2x" style="color:#875A7B;"/>
+                        </td>
+                        <td class="oe_mt8" style="font-size: 18px;font-family:times new roman;">
+                            It is touch screen enable so user can add signature with touch devices.
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            <span class="fa fa-hand-o-right fa-2x" style="padding-bottom:25px;;color:#875A7B;"/>
+                        </td>
+                        <td class="oe_mt8" style="font-size: 18px;font-family:times new roman;">
+                            Digital signature can be very useful for documents such as sale orders, purchase orders, invoices, payslips, procurement receipts, etc.
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+        </div>
+        <div>
+            <hr width="100%" style="border-style:solid;border-top-width:2px;" />
+            <h2 class="oe_slogan" style="margin-top:20px;margin-bottom:20px;color:#875A7B;font-family:times new roman;">
+                <span class="label" style="background:#875A7B">
+                    Usage
+                </span>
+            </h2>
+            <h3 class="oe_slogan" style="margin-top:10px;margin-bottom:10px;font-family:times new roman;">To use this module, you need to add widget="signature" to your binary field in your view.</h3>
+        </div>
+    </div>
+</section>
+<hr width="100%" style="border-style:solid;border-top-width:2px;" />
+<section class="oe_container">
+    <div style="padding-top:2px;padding-bottom:2px;background:#dcdcdc">
+        <div>
+            <h2 class="oe_slogan" style="margin-top:20px;margin-bottom:20px;color:#875A7B;font-family:times new roman;">
+                <span class="label" style="background:#875A7B">
+                    Signature Views
+                </span>
+            </h2>
+            <hr width="60%" style="border-style:solid;border-top-width:2px;" />
+        </div>
+        <div>
+            <h4 class="oe_slogan" style="font-size:24px;font-family:times new roman;">
+                <span class="label" style="background:#875A7B">
+                <span class="fa fa-star fa-spin"/>
+                    Draw Signature
+                </span>
+            </h4>
+            <p class='oe_mt32' style="text-align:center;font-size: 18px;font-family:times new roman;">As shown in the image, user can add a signature using mouse, pen, or finger.</p>
+        </div>
+        <div class="oe_spaced">
+            <img class="img img-responsive center-block" src="2.png">
+        </div>
+        <div>
+            <h4 class="oe_slogan" style="font-size:24px;font-family:times new roman;">
+                <span class="label" style="background:#875A7B">
+                <span class="fa fa-star fa-spin"/>
+                    Clear Digital Signature
+                </span>
+            </h4>
+            <p class='oe_mt32' style="text-align:center;font-size: 18px;font-family:times new roman;">User can clear signature using clear button and it will re-initialize the signature.</p>
+        </div>
+        <div class="oe_spaced">
+            <img class="img img-responsive center-block" src="3.png">
+        </div>
+   </div>
+</section>
+
+<!-- LEFT::LEFT PHOTO - MEDIUM -->
+
+<table style="font-family: 'Helvetica Neue', Helvetica, Arial,
+sans-serif;" cellpadding="0" cellspacing="0" border="0" width="100%">
+
+    <tbody>
+        <tr>
+            <td colspan="2" style="font-size:16px; color:#d74930; font-weight:bold;" width="100%" align="center">
+            <span class="fa fa-certificate fa-1x" style="color:#1f2327;">Check out our various Odoo Services below</span>
+
+            </td>
+        </tr>
+        <tr>
+            <td colspan="2" style="border-bottom:#d74930 solid 1px; line-height:10px;">&nbsp;
+             </td>
+        </tr>
+        <tr>
+            <td  colspan="2">
+                <table style="font-size:11px; color:#697582; line-height:20px;" cellpadding="0" cellspacing="0" border="0" width="100%">
+                    <tbody>
+                        <tr>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/odoo-openerp-services/consulting" style="color:#697582; text-decoration:none;">Odoo Consulting</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/odoo-openerp-services/gap-analysis" style="color:#697582; text-decoration:none;">Gap Analysis and BPR</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/odoo-openerp-services/implementation" style="color:#697582; text-decoration:none;">Odoo Implementation</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/ecommerce-integrations/magento-integration" style="color:#697582; text-decoration:none;">Integration with E-Commerce</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/trainings/openerp-odoo-technical-training" style="color:#697582; text-decoration:none;">Odoo Technical Training</a></span></td>
+                        </tr>
+                        <tr>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/odoo-openerp-services/offshore-development-partnership" style="color:#697582; text-decoration:none;">Offshore & Onsite Development</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/odoo-packages/odoo-support-package" style="color:#697582; text-decoration:none;">Support and Maintenance</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/odoo-openerp-services/hire-odoo-openerp-developer" style="color:#697582; text-decoration:none;">Hire Dedicated Odoo Developer</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/ecommerce-integrations/paymentech-elavon-paypal-integration" style="color:#697582; text-decoration:none;">Payment Gateway Integrations</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/trainings/openerpodoo-functional-training" style="color:#697582; text-decoration:none;">Odoo Functional Training</a></span></td>
+                        </tr>
+                        <tr>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/community/aios-mobile-client" style="color:#697582; text-decoration:none;">Odoo Mobile App Development</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.odooqa.com/page/manual-functional-testing" style="color:#697582; text-decoration:none;">Odoo Manual Testing</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.odooqa.com/page/automated-functional-testing" style="color:#697582; text-decoration:none;">Odoo Automated Testing</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/ecommerce-integrations/fedex-ups-usps-integration" style="color:#697582; text-decoration:none;">Integration with Shipping API</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com/services/odoo-openerp-services/installation-and-hosting" style="color:#697582; text-decoration:none;">Installation & Hosting</a></span></td>
+                        </tr>
+                    </tbody>
+                </table>
+            </td>
+        </tr>
+        <tr>
+            <td colspan="2" style="border-bottom:#d74930 solid 1px; line-height:10px;">&nbsp;
+             </td>
+        </tr>
+
+
+        <tr>
+            <td colspan="2" style="font-size:16px; color:#d74930; font-weight:bold;" width="100%" align="center">
+            <span class="fa fa-certificate fa-1x" style="color:#1f2327;">Check out our various Odoo Based Products</span    >
+
+            </td>
+        </tr>
+        <tr>
+            <td colspan="2" style="border-bottom:#d74930 solid 1px; line-height:10px;">&nbsp;
+             </td>
+        </tr>
+        <tr>
+            <td  colspan="2">
+                <table style="font-size:11px; color:#697582; line-height:20px;" cellpadding="0" cellspacing="0" border="0" width="100%">
+                    <tbody>
+                        <tr>
+                            <td width="20%"><span class="fa fa-coffee fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/restaurant-management-system" style="color:#697582; text-decoration:none;">Restaurant Management</a></span></td>
+                            <td width="20%"><span class="fa fa-building fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/property-management-system" style="color:#697582; text-decoration:none;">Property Management</a></span></td>
+                            <td width="20%"><span class="fa fa-plane fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/visa-management-system" style="color:#697582; text-decoration:none;">Visa Agency Management</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/odoo-singapore-localization" style="color:#697582; text-decoration:none;">Singapore Localization</a></span></td>
+                            <td width="20%"><span class="fa fa-graduation-cap fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/education-management-system" style="color:#697582; text-decoration:none;">School, University Management</a></span></td>
+                        </tr>
+                        <tr>
+                            <td width="20%"><span class="fa fa-cubes fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/construction-management-system" style="color:#697582; text-decoration:none;">Construction Management</a></span></td>
+                            <td width="20%"><span class="fa fa-bus fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/fleet-management-systems" style="color:#697582; text-decoration:none;">Fleet Management</a></span></td>
+                            <td width="20%"><span class="fa fa-stethoscope fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/healthcare-management-software" style="color:#697582; text-decoration:none;">Medical ERP</a></span></td>
+                            <td width="20%"><span class="fa fa-hotel fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/hotel-management-system" style="color:#697582; text-decoration:none;">Hotel Management</a></span></td>
+                            <td width="20%"><span class="fa fa-users fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/human-resource-management-system" style="color:#697582; text-decoration:none;">Human Resource Management</a></span></td>
+                        </tr>
+                        <tr>
+                            <td width="20%"><span class="fa fa-map-marker fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/odoo-fieldstaff-tracking-system" style="color:#697582; text-decoration:none;">Field Service Management</a></span></td>
+                            <td width="20%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/spa-salon-management-system" style="color:#697582; text-decoration:none;">Spa & Salone Management</a></span></td>
+                            <td width="20%"><span class="fa fa-phone fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/odoo-ringcentral-integration" style="color:#697582; text-decoration:none;">Ringcentral telephone integration</a></span></td>
+                            <td width="20%"><span class="fa fa-heart fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/customer-relationship-management" style="color:#697582; text-decoration:none;">Customer Relationship</a></span></td>
+                            <td width="20%"><span class="fa fa-user fa-1x" style="color:#697582;"> <a href="http://serpentcs.in/product/talent-management-system" style="color:#697582; text-decoration:none;">Talent Management</a></span></td>
+                        </tr>
+                    </tbody>
+                </table>
+            </td>
+        </tr>
+
+        <tr>
+            <td colspan="2" style="border-bottom:#d74930 solid 1px; line-height:10px;">&nbsp;
+             </td>
+        </tr>
+
+        <tr>
+            <td colspan="2" style="font-size:16px; color:#d74930; font-weight:bold;" width="100%" align="center">
+            <span class="fa fa-certificate fa-1x" style="color:#1f2327;">For any questions, support and development contact us.</span>
+
+            </td>
+        </tr>
+        <tr>
+            <td colspan="2" style="border-bottom:#d74930 solid 1px; line-height:10px;">&nbsp;
+             </td>
+        </tr>
+
+        <tr>
+            <td  colspan="2">
+                <table style="font-size:11px; color:#697582; line-height:20px;" cellpadding="0" cellspacing="0" border="0" width="100%">
+                    <tbody>
+                        <tr>
+                            <td width="38%"><span class="fa fa-whatsapp fa-1x" style="color:#697582;"> (+91) 98793-54457, (+91) 90334-72982 </span>
+                            <span class="fa fa-phone fa-1x" style="color:#697582;"> +91-79-2975-0867</span></td>
+                            <td width="17%"><span class="fa fa-envelope fa-1x" style="color:#697582;"> <a href="mailto:contact@serpentcs.com" style="color:#697582; text-decoration:none;">contact@serpentcs.com</a></span></td>
+                            <td width="15%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.com" style="color:#697582; text-decoration:none;">www.serpentcs.com</a></span></td>
+                            <td width="15%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.serpentcs.in" style="color:#697582; text-decoration:none;">www.serpentcs.in</a></span></td>
+                            <td width="15%"><span class="fa fa-globe fa-1x" style="color:#697582;"> <a href="http://www.odooqa.com" style="color:#697582; text-decoration:none;">www.odooqa.com</a></span></td>
+                        </tr>
+                        <tr>
+                            <td width="38%"><span class="fa fa-hand-pointer-o fa-1x" style="color:#697582;"> WhatsApp, Telegram or Call 24X7</span></td>
+                            <td width="17%"><span class="fa fa-hand-pointer-o fa-1x" style="color:#697582;"> Help! Send Mail</span></td>
+                            <td width="15%"><span class="fa fa-hand-pointer-o fa-1x" style="color:#697582;"> Browse our Services</span></td>
+                            <td width="15%"><span class="fa fa-hand-pointer-o fa-1x" style="color:#697582;"> Browse our Products</span></td>
+                            <td width="15%"><span class="fa fa-hand-pointer-o fa-1x" style="color:#697582;"> QA Services</span></td>
+                        </tr>
+                    </tbody>
+                </table>
+            </td>
+        </tr>
+        <tr colspan="2">
+            <td  colspan="2" style="border-bottom:#d74930 solid 1px; line-height:10px;">&nbsp;
+            </td>
+        </tr>
+        <tr colspan="2">
+            <td  colspan="2" style="padding-top:9px;">
+                <a href="https://www.facebook.com/Serpent-Consulting-Services-Pvt-Ltd-299017730141795/" style="text-decoration:none;"><img src="fb.jpg" style="vertical-align:middle; border-radius: 50%;" width="25">
+                </a>
+                <a href="https://twitter.com/Serpent_CS" style="text-decoration:none;"><img src="twitter.jpg" style="vertical-align:middle; border-radius: 50%;" width="25">
+                </a>
+                <a href="https://www.linkedin.com/company-beta/2435682/" style="text-decoration:none;"><img src="in.jpg" style="vertical-align:middle; border-radius: 50%;" width="25">
+                </a>
+                <a href="https://www.youtube.com/user/husendaudi123/videos" style="text-decoration:none;"><img src="youtube.jpg" style="vertical-align:middle; border-radius: 50%;" width="25">
+                </a>
+                <a href="https://www.instagram.com/explore/tags/serpentcs/"style="text-decoration:none;"><img src="insta.jpg" style="vertical-align:middle; border-radius: 50%;" width="25">
+                </a>
+                <a href="http://www.serpentcs.com"style="text-decoration:none;"><img src="serpent32.png" style="vertical-align:middle; border-radius: 50%;" width="25">
+                </a>
+                <a href="http://www.serpentcs.in"style="text-decoration:none;"><img src="serpent32.png" style="vertical-align:middle; border-radius: 50%;" width="25">
+                </a>
+                <a href="http://www.odooqa.com"style="text-decoration:none;"><img src="odooqa32.png" style="vertical-align:middle; border-radius: 50%;" width="25">
+                </a>
+            </td>
+        </tr>
+        <tr colspan="2">
+            <td  colspan="2" style="font-size:8px; color:#697582; line-height:15px;padding-top:10px; text-align:justify;">
+            </td>
+        </tr>
+    </tbody>
+</table>

BIN
static/description/insta.jpg


BIN
static/description/odooqa32.png


BIN
static/description/serpent32.png


BIN
static/description/twitter.jpg


BIN
static/description/youtube.jpg


+ 1392 - 0
static/lib/jSignatureCustom.js

@@ -0,0 +1,1392 @@
+/** @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 <http://www.opensource.org/licenses/mit-license.php> 
+
+*/
+;(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 $('<div style="'+controlbarstyle+'"></div>').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 $('<div style="'+controlbarstyle+'"></div>').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(".o_form_view")
+            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)
+
+})();

+ 10 - 0
static/src/css/digital.css

@@ -0,0 +1,10 @@
+/* Styles for signature plugin v1.1.0. */
+/* .kbw-signature {
+	height : "100px";
+	width : "150px";
+} */
+
+
+.openerp .oe_form_readonly .oe_edit_only, .openerp .oe_form_readonly .oe_form_field:empty {
+  display: none !important;
+}

File diff suppressed because it is too large
+ 516 - 0
static/src/css/jquery-ui.css


+ 5 - 0
static/src/css/jquery.signature.css

@@ -0,0 +1,5 @@
+/* Styles for signature plugin v1.1.0. */
+.kbw-signature {
+	display: inline-block;
+	border: 1px solid #a0a0a0;
+}

BIN
static/src/img/icon.png


+ 103 - 0
static/src/js/digital_sign.js

@@ -0,0 +1,103 @@
+openerp.web_digital_sign = function(instance) {
+    var _t = instance.web._t;
+    var QWeb = instance.web.qweb;
+    var images = {}
+    instance.web.form.widgets.add('signature', 'instance.web.form.FieldSignature');
+
+    instance.web.form.FieldSignature = instance.web.form.FieldBinaryImage.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') && !instance.web.form.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;
+            }
+            var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url }));
+            this.$el.find('img').remove();
+            if(this.view.get("actual_mode") !== 'edit' && this.view.get("actual_mode") !== 'create'){
+            	this.$el.find('.signature > canvas').remove();
+            	this.$el.prepend($img);
+            }
+            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 instance.web.Model(this.view.dataset.model).call("read", [this.view.datarecord.id, [field_name]]).done(function(data) {
+                        if (data) {
+//                            self.$el.find(".signature").jSignature("reset");
+                            self.$el.find(".signature").jSignature("setData",'data:image/png;base64,'+data[field_name]);
+                        }
+                    });
+                } 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'});
+              }
+              else if(this.get('value')){
+                  this.$el.prepend($img);
+              }
+          }
+        }
+    });
+    
+    instance.web.FormView.include({
+        save: function(prepend_on_create) {
+            var self = this;
+            $('.save_sign').click()
+            return this._super.apply(this, arguments);
+        },
+    })
+};

+ 26 - 0
static/src/xml/digital_sign.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<templates id="template" xml:space="preserve">
+   <t t-name="FieldSignature">
+        <div class="panel panel-default mt16 mb0 " id="drawsign">
+            <div class="panel-heading oe_edit_only digital_pennal">
+                <div style="float: right;">
+                    <a id="sign_clean" class="btn btn-xs oe_edit_only">Clear</a>
+                    <a class="oe_edit_only save_sign"></a>
+                </div>
+                <strong>Draw your signature</strong>
+            </div>
+            <div class="signature panel-body"></div>
+        </div>
+    </t>
+    <t t-name="FieldBinaryImage-extend">
+        <img t-att-src='url'
+            t-att-border="widget.readonly ? 0 : 1"
+            t-att-name="widget.name"
+            t-att-width="widget.node.attrs.img_width || widget.node.attrs.width"
+            t-att-height="widget.node.attrs.img_height || widget.node.attrs.height"
+            t-att-tabindex="widget.node.attrs.img_tabindex || widget.node.attrs.tabindex"
+        />
+    </t>
+</templates>
+

+ 31 - 0
users.py

@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#    
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
+#    Copyright (C) 2011-2015 Serpent Consulting Services Pvt. Ltd. (<http://www.serpentcs.com>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp import models, fields, api
+
+class Users(models.Model):
+    _name = 'res.users'
+    _inherit = 'res.users'
+
+    signature_image= fields.Binary(string='Signature')
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

BIN
users.pyc


+ 21 - 0
users_view.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" ?>
+<openerp>
+	<data>
+
+		<record id="inherited_res_users_form" model="ir.ui.view">
+            <field name="name">inherited.res.users.form</field>
+            <field name="model">res.users</field>
+            <field name="inherit_id" ref="base.view_users_form"/>
+            <field name="arch" type="xml">
+		<xpath expr="//field[@name='signature']" position="attributes">
+			<attribute name="invisible">1</attribute>
+                </xpath>
+            	<xpath expr="//field[@name='signature']" position="after">
+			<label for="signature_image" class="oe_edit_only"/>
+			<h2><field name="signature_image" widget="signature"/></h2>
+                </xpath>
+            </field>
+        </record>
+
+	</data>
+</openerp>

+ 11 - 0
views/we_digital_sign_view.xml

@@ -0,0 +1,11 @@
+<openerp>
+    <data>
+        <template id="sale_order_backend" name="sale_order assets" inherit_id="web.assets_backend">
+            <xpath expr="." position="inside">
+                <link rel="stylesheet" href="/web_digital_sign/static/src/css/digital.css"/>
+                <script type="text/javascript" src="/web_digital_sign/static/lib/jSignatureCustom.js"></script>
+                <script type="text/javascript" src="/web_digital_sign/static/src/js/digital_sign.js"></script>
+            </xpath>
+        </template>
+    </data>
+</openerp>

Some files were not shown because too many files changed in this diff