[FIX] translation marks for db notation.
[odoo/odoo.git] / addons / web / static / src / js / chrome.js
1 /*---------------------------------------------------------
2  * OpenERP Web chrome
3  *---------------------------------------------------------*/
4 openerp.web.chrome = function(openerp) {
5 var QWeb = openerp.web.qweb,
6     _t = openerp.web._t;
7
8 openerp.web.Notification =  openerp.web.Widget.extend(/** @lends openerp.web.Notification# */{
9     template: 'Notification',
10     identifier_prefix: 'notification-',
11
12     init: function() {
13         this._super.apply(this, arguments);
14         openerp.notification = this;
15     },
16
17     start: function() {
18         this._super.apply(this, arguments);
19         this.$element.notify({
20             speed: 500,
21             expires: 1500
22         });
23     },
24     notify: function(title, text) {
25         this.$element.notify('create', {
26             title: title,
27             text: text
28         });
29     },
30     warn: function(title, text) {
31         this.$element.notify('create', 'oe_notification_alert', {
32             title: title,
33             text: text
34         });
35     }
36
37 });
38
39 openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# */{
40     dialog_title: "",
41     identifier_prefix: 'dialog',
42     /**
43      * @constructs openerp.web.Dialog
44      * @extends openerp.web.OldWidget
45      *
46      * @param parent
47      * @param dialog_options
48      */
49     init: function (parent, dialog_options) {
50         var self = this;
51         this._super(parent);
52         this.dialog_options = {
53             modal: true,
54             destroy_on_close: true,
55             width: $(window).width() * (($(window).width() > 1024) ? 0.5 : 0.75),
56             min_width: 0,
57             max_width: '95%',
58             height: 'auto',
59             min_height: 0,
60             max_height: '95%',
61             autoOpen: false,
62             position: [false, 50],
63             autoResize : 'auto',
64             buttons: {},
65             beforeClose: function () { self.on_close(); },
66             resizeStop: this.on_resized
67         };
68         for (var f in this) {
69             if (f.substr(0, 10) == 'on_button_') {
70                 this.dialog_options.buttons[f.substr(10)] = this[f];
71             }
72         }
73         if (dialog_options) {
74             this.set_options(dialog_options);
75         }
76     },
77     set_options: function(options) {
78         options = options || {};
79         options.width = this.get_width(options.width || this.dialog_options.width);
80         options.min_width = this.get_width(options.min_width || this.dialog_options.min_width);
81         options.max_width = this.get_width(options.max_width || this.dialog_options.max_width);
82         options.height = this.get_height(options.height || this.dialog_options.height);
83         options.min_height = this.get_height(options.min_height || this.dialog_options.min_height);
84         options.max_height = this.get_height(options.max_height || this.dialog_options.max_height);
85
86         if (options.width !== 'auto') {
87             if (options.width > options.max_width) options.width = options.max_width;
88             if (options.width < options.min_width) options.width = options.min_width;
89         }
90         if (options.height !== 'auto') {
91             if (options.height > options.max_height) options.height = options.max_height;
92             if (options.height < options.min_height) options.height = options.min_height;
93         }
94         if (!options.title && this.dialog_title) {
95             options.title = this.dialog_title;
96         }
97         _.extend(this.dialog_options, options);
98     },
99     get_width: function(val) {
100         return this.get_size(val.toString(), $(window.top).width());
101     },
102     get_height: function(val) {
103         return this.get_size(val.toString(), $(window.top).height());
104     },
105     get_size: function(val, available_size) {
106         if (val === 'auto') {
107             return val;
108         } else if (val.slice(-1) == "%") {
109             return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10));
110         } else {
111             return parseInt(val, 10);
112         }
113     },
114     start: function () {
115         this.$element.dialog(this.dialog_options);
116         this._super();
117         return this;
118     },
119     open: function(dialog_options) {
120         // TODO fme: bind window on resize
121         if (this.template) {
122             this.$element.html(this.render());
123         }
124         this.set_options(dialog_options);
125         this.$element.dialog(this.dialog_options).dialog('open');
126         return this;
127     },
128     close: function() {
129         this.$element.dialog('close');
130     },
131     on_close: function() {
132         if (this.dialog_options.destroy_on_close) {
133             this.$element.dialog('destroy');
134         }
135     },
136     on_resized: function() {
137         if (openerp.connection.debug) {
138             console.log("Dialog resized to %d x %d", this.$element.width(), this.$element.height());
139         }
140     },
141     stop: function () {
142         // Destroy widget
143         this.close();
144         this.$element.dialog('destroy');
145         this._super();
146     }
147 });
148
149 openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({
150     init: function() {
151         this._super();
152         openerp.connection.on_rpc_error.add(this.on_rpc_error);
153     },
154     on_rpc_error: function(error) {
155         this.error = error;
156         if (error.data.fault_code) {
157             var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
158             if (split.length > 1) {
159                 error.type = split.shift();
160                 error.data.fault_code = error.data.fault_code.substr(error.type.length + 4);
161             }
162         }
163         if (error.code === 200 && error.type) {
164             this.on_managed_error(error);
165         } else {
166             this.on_traceback(error);
167         }
168     },
169     on_managed_error: function(error) {
170         $('<div>' + QWeb.render('CrashManagerWarning', {error: error}) + '</div>').dialog({
171             title: "OpenERP " + _.str.capitalize(error.type),
172             buttons: [
173                 {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
174             ]
175         });
176     },
177     on_traceback: function(error) {
178         var self = this;
179         var buttons = {};
180         if (openerp.connection.openerp_entreprise) {
181             buttons[_t("Send OpenERP Enterprise Report")] = function() {
182                 $this = $(this);
183                 var issuename = $('#issuename').val();
184                 var explanation = $('#explanation').val();
185                 var remark = $('#remark').val();
186                 // Call the send method from server to send mail with details
187                 new openerp.web.DataSet(self, 'publisher_warranty.contract').call_and_eval('send', [error.data,explanation,remark,issuename]).then(function(result){
188                     if (result === false) {
189                         alert('There was a communication error.')
190                     } else {
191                         $this.dialog('close');
192                     }
193                 });
194             };
195             buttons[_t("Dont send")] = function() {
196                 $(this).dialog("close");
197             };
198         } else {
199             buttons[_t("Ok")] = function() {
200                 $(this).dialog("close");
201             };
202         }
203         var dialog = new openerp.web.Dialog(this, {
204             title: "OpenERP " + _.str.capitalize(this.error.type),
205             autoOpen: true,
206             width: '80%',
207             height: '50%',
208             min_width: '800px',
209             min_height: '600px',
210             buttons: buttons
211         }).start();
212         dialog.$element.html(QWeb.render('CrashManagerError', {session: openerp.connection, error: error}));
213     },
214 });
215
216 openerp.web.Loading = openerp.web.Widget.extend(/** @lends openerp.web.Loading# */{
217     template: 'Loading',
218     /**
219      * @constructs openerp.web.Loading
220      * @extends openerp.web.Widget
221      *
222      * @param parent
223      * @param element_id
224      */
225     init: function(parent) {
226         this._super(parent);
227         this.count = 0;
228         this.blocked_ui = false;
229         var self = this;
230         this.request_call = function() {
231             self.on_rpc_event(1);
232         };
233         this.response_call = function() {
234             self.on_rpc_event(-1);
235         };
236         this.session.on_rpc_request.add_first(this.request_call);
237         this.session.on_rpc_response.add_last(this.response_call);
238     },
239     stop: function() {
240         this.session.on_rpc_request.remove(this.request_call);
241         this.session.on_rpc_response.remove(this.response_call);
242         this.on_rpc_event(-this.count);
243         this._super();
244     },
245     on_rpc_event : function(increment) {
246         var self = this;
247         if (!this.count && increment === 1) {
248             // Block UI after 3s
249             this.long_running_timer = setTimeout(function () {
250                 self.blocked_ui = true;
251                 $.blockUI();
252             }, 3000);
253         }
254
255         this.count += increment;
256         if (this.count > 0) {
257             //this.$element.html(QWeb.render("Loading", {}));
258             $(".loading",this.$element).html("Loading ("+this.count+")");
259             $(".loading",this.$element).show();
260             this.widget_parent.$element.addClass('loading');
261         } else {
262             this.count = 0;
263             clearTimeout(this.long_running_timer);
264             // Don't unblock if blocked by somebody else
265             if (self.blocked_ui) {
266                 this.blocked_ui = false;
267                 $.unblockUI();
268             }
269             $(".loading",this.$element).fadeOut();
270             this.widget_parent.$element.removeClass('loading');
271         }
272     }
273 });
274
275 openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database# */{
276     template: "DatabaseManager",
277     /**
278      * @constructs openerp.web.Database
279      * @extends openerp.web.Widget
280      *
281      * @param parent
282      * @param element_id
283      * @param option_id
284      */
285     init: function(parent, element_id, option_id) {
286         this._super(parent, element_id);
287         this.unblockUIFunction = $.unblockUI;
288     },
289     start: function() {
290         this.$option_id = $("#oe_db_options");
291
292         var self = this;
293         var fetch_db = this.rpc("/web/database/get_list", {}, function(result) {
294             self.db_list = result.db_list;
295         });
296         var fetch_langs = this.rpc("/web/session/get_lang_list", {}, function(result) {
297             if (result.error) {
298                 self.display_error(result);
299                 return;
300             }
301             self.lang_list = result.lang_list;
302         });
303         $.when(fetch_db, fetch_langs).then(function () {self.do_create();});
304
305         this.$element.find('#db-create').click(this.do_create);
306         this.$element.find('#db-drop').click(this.do_drop);
307         this.$element.find('#db-backup').click(this.do_backup);
308         this.$element.find('#db-restore').click(this.do_restore);
309         this.$element.find('#db-change-password').click(this.do_change_password);
310         this.$element.find('#back-to-login').click(function() {
311             self.hide();
312         });
313     },
314     stop: function () {
315         this.hide();
316         this.$option_id.empty();
317
318         this.$element
319             .find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login')
320                 .unbind('click')
321             .end()
322             .empty();
323         this._super();
324     },
325     show: function () {
326         this.$element.closest(".login")
327                 .addClass("database_block");
328     },
329     hide: function () {
330         this.$element.closest(".login")
331                 .removeClass("database_block")
332     },
333     /**
334      * Converts a .serializeArray() result into a dict. Does not bother folding
335      * multiple identical keys into an array, last key wins.
336      *
337      * @param {Array} array
338      */
339     to_object: function (array) {
340         var result = {};
341         _(array).each(function (record) {
342             result[record.name] = record.value;
343         });
344         return result;
345     },
346     /**
347      * Waits until the new database is done creating, then unblocks the UI and
348      * logs the user in as admin
349      *
350      * @param {Number} db_creation_id identifier for the db-creation operation, used to fetch the current installation progress
351      * @param {Object} info info fields for this database creation
352      * @param {String} info.db name of the database being created
353      * @param {String} info.password super-admin password for the database
354      */
355     wait_for_newdb: function (db_creation_id, info) {
356         var self = this;
357         self.rpc('/web/database/progress', {
358             id: db_creation_id,
359             password: info.password
360         }, function (result) {
361             var progress = result[0];
362             // I'd display a progress bar, but turns out the progress status
363             // the server report kind-of blows goats: it's at 0 for ~75% of
364             // the installation, then jumps to 75%, then jumps down to either
365             // 0 or ~40%, then back up to 75%, then terminates. Let's keep that
366             // mess hidden behind a not-very-useful but not overly weird
367             // message instead.
368             if (progress < 1) {
369                 setTimeout(function () {
370                     self.wait_for_newdb(db_creation_id, info);
371                 }, 500);
372                 return;
373             }
374
375             var admin = result[1][0];
376             setTimeout(function () {
377                 self.widget_parent.do_login(
378                         info.db, admin.login, admin.password);
379                 self.stop();
380                 self.unblockUI();
381             });
382         });
383     },
384     /**
385      * Blocks UI and replaces $.unblockUI by a noop to prevent third parties
386      * from unblocking the UI
387      */
388     blockUI: function () {
389         $.blockUI();
390         $.unblockUI = function () {};
391     },
392     /**
393      * Reinstates $.unblockUI so third parties can play with blockUI, and
394      * unblocks the UI
395      */
396     unblockUI: function () {
397         $.unblockUI = this.unblockUIFunction;
398         $.unblockUI();
399     },
400     /**
401      * Displays an error dialog resulting from the various RPC communications
402      * failing over themselves
403      *
404      * @param {Object} error error description
405      * @param {String} error.title title of the error dialog
406      * @param {String} error.error message of the error dialog
407      */
408     display_error: function (error) {
409         return $('<div>').dialog({
410             modal: true,
411             title: error.title,
412             buttons: [
413                 {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
414             ]
415         }).html(error.error);
416     },
417     do_create: function() {
418         var self = this;
419         self.$option_id.html(QWeb.render("Database.CreateDB", self));
420         self.$option_id.find("form[name=create_db_form]").validate({
421             submitHandler: function (form) {
422                 var fields = $(form).serializeArray();
423                 self.blockUI();
424                 self.rpc("/web/database/create", {'fields': fields}, function(result) {
425                     if (result.error) {
426                         self.unblockUI();
427                         self.display_error(result);
428                         return;
429                     }
430                     self.db_list.push(self.to_object(fields)['db_name']);
431                     self.db_list.sort();
432                     self.widget_parent.set_db_list(self.db_list);
433                     var form_obj = self.to_object(fields);
434                     self.wait_for_newdb(result, {
435                         password: form_obj['super_admin_pwd'],
436                         db: form_obj['db_name']
437                     });
438                 });
439             }
440         });
441     },
442     do_drop: function() {
443         var self = this;
444         self.$option_id.html(QWeb.render("DropDB", self));
445         self.$option_id.find("form[name=drop_db_form]").validate({
446             submitHandler: function (form) {
447                 var $form = $(form),
448                     fields = $form.serializeArray(),
449                     $db_list = $form.find('select[name=drop_db]'),
450                     db = $db_list.val();
451
452                 if (!confirm("Do you really want to delete the database: " + db + " ?")) {
453                     return;
454                 }
455                 self.rpc("/web/database/drop", {'fields': fields}, function(result) {
456                     if (result.error) {
457                         self.display_error(result);
458                         return;
459                     }
460                     $db_list.find(':selected').remove();
461                     self.db_list.splice(_.indexOf(self.db_list, db, true), 1);
462                     self.widget_parent.set_db_list(self.db_list);
463                     self.do_notify("Dropping database", "The database '" + db + "' has been dropped");
464                 });
465             }
466         });
467     },
468     do_backup: function() {
469         var self = this;
470         self.$option_id
471             .html(QWeb.render("BackupDB", self))
472             .find("form[name=backup_db_form]").validate({
473             submitHandler: function (form) {
474                 self.blockUI();
475                 self.session.get_file({
476                     form: form,
477                     error: function (body) {
478                         var error = body.firstChild.data.split('|');
479                         self.display_error({
480                             title: error[0],
481                             error: error[1]
482                         });
483                     },
484                     complete: function() {
485                         self.unblockUI();
486                         self.do_notify(_t("Backed"), _t("Database backed up successfully"));
487                     }
488                 });
489             }
490         });
491     },
492     do_restore: function() {
493         var self = this;
494         self.$option_id.html(QWeb.render("RestoreDB", self));
495
496         self.$option_id.find("form[name=restore_db_form]").validate({
497             submitHandler: function (form) {
498                 self.blockUI();
499                 $(form).ajaxSubmit({
500                     url: '/web/database/restore',
501                     type: 'POST',
502                     resetForm: true,
503                     success: function (body) {
504                         // TODO: ui manipulations
505                         // note: response objects don't work, but we have the
506                         // HTTP body of the response~~
507
508                         // If empty body, everything went fine
509                         if (!body) { return; }
510
511                         if (body.indexOf('403 Forbidden') !== -1) {
512                             self.display_error({
513                                 title: 'Access Denied',
514                                 error: 'Incorrect super-administrator password'
515                             })
516                         } else {
517                             self.display_error({
518                                 title: 'Restore Database',
519                                 error: 'Could not restore the database'
520                             })
521                         }
522                     },
523                     complete: function() {
524                         self.unblockUI();
525                         self.do_notify(_t("Restored"), _t("Database restored successfully"));
526                     }
527                 });
528             }
529         });
530     },
531     do_change_password: function() {
532         var self = this;
533         self.$option_id.html(QWeb.render("Change_DB_Pwd", self));
534
535         self.$option_id.find("form[name=change_pwd_form]").validate({
536             messages: {
537                 old_pwd: "Please enter your previous password",
538                 new_pwd: "Please enter your new password",
539                 confirm_pwd: {
540                     required: "Please confirm your new password",
541                     equalTo: "The confirmation does not match the password"
542                 }
543             },
544             submitHandler: function (form) {
545                 self.rpc("/web/database/change_password", {
546                     'fields': $(form).serializeArray()
547                 }, function(result) {
548                     if (result.error) {
549                         self.display_error(result);
550                         return;
551                     }
552                     self.do_notify("Changed Password", "Password has been changed successfully");
553                 });
554             }
555         });
556     }
557 });
558
559 openerp.web.Login =  openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
560     remember_credentials: true,
561     
562     template: "Login",
563     identifier_prefix: 'oe-app-login-',
564     /**
565      * @constructs openerp.web.Login
566      * @extends openerp.web.Widget
567      *
568      * @param parent
569      * @param element_id
570      */
571
572     init: function(parent) {
573         this._super(parent);
574         this.has_local_storage = typeof(localStorage) != 'undefined';
575         this.selected_db = null;
576         this.selected_login = null;
577
578         if (this.has_local_storage && this.remember_credentials) {
579             this.selected_db = localStorage.getItem('last_db_login_success');
580             this.selected_login = localStorage.getItem('last_login_login_success');
581             if (jQuery.deparam(jQuery.param.querystring()).debug != undefined) {
582                 this.selected_password = localStorage.getItem('last_password_login_success');
583             }
584         }
585     },
586     start: function() {
587         var self = this;
588         this.database = new openerp.web.Database(this);
589         this.database.appendTo(this.$element);
590
591         this.$element.find('#oe-db-config').click(function() {
592             self.database.show();
593         });
594
595         this.$element.find("form").submit(this.on_submit);
596
597         this.rpc("/web/database/get_list", {}, function(result) {
598             self.set_db_list(result.db_list);
599         }, 
600         function(error, event) {
601             if (error.data.fault_code === 'AccessDenied') {
602                 event.preventDefault();
603             }
604         });
605
606     },
607     set_db_list: function (list) {
608         this.$element.find("[name=db]").replaceWith(
609             openerp.web.qweb.render('Login_dblist', {
610                 db_list: list, selected_db: this.selected_db}))
611     },
612     on_submit: function(ev) {
613         if(ev) {
614             ev.preventDefault();
615         }
616         var $e = this.$element;
617         var db = $e.find("form [name=db]").val();
618         var login = $e.find("form input[name=login]").val();
619         var password = $e.find("form input[name=password]").val();
620
621         this.do_login(db, login, password);
622     },
623     /**
624      * Performs actual login operation, and UI-related stuff
625      *
626      * @param {String} db database to log in
627      * @param {String} login user login
628      * @param {String} password user password
629      */
630     do_login: function (db, login, password) {
631         var self = this;
632         this.session.on_session_invalid.add({
633             callback: function () {
634                 self.$element.addClass("login_invalid");
635             },
636             unique: true
637         });
638         this.session.session_authenticate(db, login, password).then(function() {
639             self.$element.removeClass("login_invalid");
640             if (self.has_local_storage) {
641                 if(self.remember_credentials) {
642                     localStorage.setItem('last_db_login_success', db);
643                     localStorage.setItem('last_login_login_success', login);
644                     if (jQuery.deparam(jQuery.param.querystring()).debug != undefined) {
645                         localStorage.setItem('last_password_login_success', password);
646                     }
647                 } else {
648                     localStorage.setItem('last_db_login_success', '');
649                     localStorage.setItem('last_login_login_success', '');
650                     localStorage.setItem('last_password_login_success', '');
651                 }
652             }
653         });
654     },
655 });
656
657 openerp.web.Header =  openerp.web.Widget.extend(/** @lends openerp.web.Header# */{
658     template: "Header",
659     identifier_prefix: 'oe-app-header-',
660     /**
661      * @constructs openerp.web.Header
662      * @extends openerp.web.Widget
663      *
664      * @param parent
665      */
666     init: function(parent) {
667         this._super(parent);
668         this.qs = "?" + jQuery.param.querystring();
669         this.$content = $();
670         this.update_promise = $.Deferred().resolve();
671     },
672     start: function() {
673         this._super();
674     },
675     do_update: function () {
676         var self = this;
677         var fct = function() {
678             self.$content.remove();
679             if (!self.session.uid)
680                 return;
681             var func = new openerp.web.Model("res.users").get_func("read");
682             return func(self.session.uid, ["name", "company_id"]).pipe(function(res) {
683                 self.$content = $(QWeb.render("Header-content", {widget: self, user: res}));
684                 self.$content.appendTo(self.$element);
685                 self.$element.find(".logout").click(self.on_logout);
686                 self.$element.find("a.preferences").click(self.on_preferences);
687                 self.$element.find(".about").click(self.on_about);
688                 return self.shortcut_load();
689             });
690         };
691         this.update_promise = this.update_promise.pipe(fct, fct);
692     },
693     on_about: function() {
694         var self = this;
695         self.rpc("/web/webclient/version_info", {}).then(function(res) {
696             var $help = $(QWeb.render("About-Page", {version_info: res}));
697             $help.dialog({autoOpen: true,
698                 modal: true, width: 960, title: _t("About")});
699         });
700     },
701     shortcut_load :function(){
702         var self = this,
703             sc = self.session.shortcuts,
704             shortcuts_ds = new openerp.web.DataSet(this, 'ir.ui.view_sc');
705         // TODO: better way to communicate between sections.
706         // sc.bindings, because jquery does not bind/trigger on arrays...
707         if (!sc.binding) {
708             sc.binding = {};
709             $(sc.binding).bind({
710                 'add': function (e, attrs) {
711                     shortcuts_ds.create(attrs, function (out) {
712                         $('<li>', {
713                             'data-shortcut-id':out.result,
714                             'data-id': attrs.res_id
715                         }).text(attrs.name)
716                           .appendTo(self.$element.find('.oe-shortcuts ul'));
717                         attrs.id = out.result;
718                         sc.push(attrs);
719                     });
720                 },
721                 'remove-current': function () {
722                     var menu_id = self.session.active_id;
723                     var $shortcut = self.$element
724                         .find('.oe-shortcuts li[data-id=' + menu_id + ']');
725                     var shortcut_id = $shortcut.data('shortcut-id');
726                     $shortcut.remove();
727                     shortcuts_ds.unlink([shortcut_id]);
728                     var sc_new = _.reject(sc, function(shortcut){ return shortcut_id === shortcut.id});
729                     sc.splice(0, sc.length);
730                     sc.push.apply(sc, sc_new);
731                     }
732             });
733         }
734         return this.rpc('/web/session/sc_list', {}, function(shortcuts) {
735             sc.splice(0, sc.length);
736             sc.push.apply(sc, shortcuts);
737
738             self.$element.find('.oe-shortcuts')
739                 .html(QWeb.render('Shortcuts', {'shortcuts': shortcuts}))
740                 .undelegate('li', 'click')
741
742                 .delegate('li', 'click', function(e) {
743                     e.stopPropagation();
744                     var id = $(this).data('id');
745                     self.session.active_id = id;
746                     self.rpc('/web/menu/action', {'menu_id':id}, function(ir_menu_data) {
747                         if (ir_menu_data.action.length){
748                             self.on_action(ir_menu_data.action[0][2]);
749                         }
750                     });
751                 });
752         });
753     },
754
755     on_action: function(action) {
756     },
757     on_preferences: function(){
758         var self = this;
759         var action_manager = new openerp.web.ActionManager(this);
760         var dataset = new openerp.web.DataSet (this,'res.users',this.context);
761         dataset.call ('action_get','',function (result){
762             self.rpc('/web/action/load', {action_id:result}, function(result){
763                 action_manager.do_action(_.extend(result['result'], {
764                     res_id: self.session.uid,
765                     res_model: 'res.users',
766                     flags: {
767                         action_buttons: false,
768                         search_view: false,
769                         sidebar: false,
770                         views_switcher: false,
771                         pager: false
772                     }
773                 }));
774             });
775         });
776         this.dialog = new openerp.web.Dialog(this,{
777             title: _t("Preferences"),
778             width: '700px',
779             buttons: [
780                 {text: _t("Change password"), click: function(){ self.change_password(); }},
781                 {text: _t("Cancel"), click: function(){ $(this).dialog('destroy'); }},
782                 {text: _t("Save"), click: function(){
783                         var inner_viewmanager = action_manager.inner_viewmanager;
784                         inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save()
785                         .then(function() {
786                             self.dialog.stop();
787                             window.location.reload();
788                         });
789                     }
790                 }
791             ]
792         });
793        this.dialog.start().open();
794        action_manager.appendTo(this.dialog);
795        action_manager.render(this.dialog);
796     },
797
798     change_password :function() {
799         var self = this;
800         this.dialog = new openerp.web.Dialog(this, {
801             title: _t("Change Password"),
802             width : 'auto'
803         });
804         this.dialog.start().open();
805         this.dialog.$element.html(QWeb.render("Change_Pwd", self));
806         this.dialog.$element.find("form[name=change_password_form]").validate({
807             submitHandler: function (form) {
808                 self.rpc("/web/session/change_password",{
809                     'fields': $(form).serializeArray()
810                 }, function(result) {
811                     if (result.error) {
812                         self.display_error(result);
813                         return;
814                     } else {
815                         openerp.webclient.on_logout();
816                     }
817                 });
818             }
819         });
820     },
821     display_error: function (error) {
822         return $('<div>').dialog({
823             modal: true,
824             title: error.title,
825             buttons: [
826                 {text: _("Ok"), click: function() { $(this).dialog("close"); }}
827             ]
828         }).html(error.error);
829     },
830     on_logout: function() {
831     }
832 });
833
834 openerp.web.Menu =  openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
835     /**
836      * @constructs openerp.web.Menu
837      * @extends openerp.web.Widget
838      *
839      * @param parent
840      * @param element_id
841      * @param secondary_menu_id
842      */
843     init: function(parent, element_id, secondary_menu_id) {
844         this._super(parent, element_id);
845         this.secondary_menu_id = secondary_menu_id;
846         this.$secondary_menu = $("#" + secondary_menu_id);
847         this.menu = false;
848         this.folded = false;
849         if (window.localStorage) {
850             this.folded = localStorage.getItem('oe_menu_folded') === 'true';
851         }
852         this.float_timeout = 700;
853     },
854     start: function() {
855         this.$secondary_menu.addClass(this.folded ? 'oe_folded' : 'oe_unfolded');
856     },
857     do_reload: function() {
858         var self = this;
859         return this.rpc("/web/menu/load", {}, this.on_loaded).then(function () {
860             if (self.current_menu) {
861                 self.open_menu(self.current_menu);
862             }
863         });
864     },
865     on_loaded: function(data) {
866         this.data = data;
867         this.$element.html(QWeb.render("Menu", { widget : this }));
868         this.$secondary_menu.html(QWeb.render("Menu.secondary", { widget : this }));
869         this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click);
870         this.$secondary_menu.find('.oe_toggle_secondary_menu').click(this.on_toggle_fold);
871     },
872     on_toggle_fold: function() {
873         this.$secondary_menu.toggleClass('oe_folded').toggleClass('oe_unfolded');
874         if (this.folded) {
875             this.$secondary_menu.find('.oe_secondary_menu.active').show();
876         } else {
877             this.$secondary_menu.find('.oe_secondary_menu').hide();
878         }
879         this.folded = !this.folded;
880         if (window.localStorage) {
881             localStorage.setItem('oe_menu_folded', this.folded.toString());
882         }
883     },
884     /**
885      * Opens a given menu by id, as if a user had browsed to that menu by hand
886      * except does not trigger any event on the way
887      *
888      * @param {Number} menu_id database id of the terminal menu to select
889      */
890     open_menu: function (menu_id) {
891         this.$element.add(this.$secondary_menu).find('.active')
892                 .removeClass('active');
893         this.$secondary_menu.find('> .oe_secondary_menu').hide();
894
895         var $primary_menu;
896         var $secondary_submenu = this.$secondary_menu.find(
897                 'a[data-menu=' + menu_id +']');
898         if ($secondary_submenu.length) {
899             for(;;) {
900                 if ($secondary_submenu.hasClass('leaf')) {
901                     $secondary_submenu.addClass('active');
902                 } else if ($secondary_submenu.hasClass('submenu')) {
903                     $secondary_submenu.addClass('opened')
904                 }
905                 var $parent = $secondary_submenu.parent().show();
906                 if ($parent.hasClass('oe_secondary_menu')) {
907                     var primary_id = $parent.data('menu-parent');
908                     $primary_menu = this.$element.find(
909                             'a[data-menu=' + primary_id + ']');
910                     break;
911                 }
912                 $secondary_submenu = $parent.prev();
913             }
914         } else {
915             $primary_menu = this.$element.find('a[data-menu=' + menu_id + ']');
916         }
917         if (!$primary_menu.length) {
918             return;
919         }
920         $primary_menu.addClass('active');
921         this.$secondary_menu.find(
922             'div[data-menu-parent=' + $primary_menu.data('menu') + ']').show();
923     },
924     on_menu_click: function(ev, id) {
925         id = id || 0;
926         var $clicked_menu, manual = false;
927
928         if (id) {
929             // We can manually activate a menu with it's id (for hash url mapping)
930             manual = true;
931             $clicked_menu = this.$element.find('a[data-menu=' + id + ']');
932             if (!$clicked_menu.length) {
933                 $clicked_menu = this.$secondary_menu.find('a[data-menu=' + id + ']');
934             }
935         } else {
936             $clicked_menu = $(ev.currentTarget);
937             id = $clicked_menu.data('menu');
938         }
939
940         if (this.do_menu_click($clicked_menu, manual) && id) {
941             this.current_menu = id;
942             this.session.active_id = id;
943             this.rpc('/web/menu/action', {'menu_id': id}, this.on_menu_action_loaded);
944         }
945         if (ev) {
946             ev.stopPropagation();
947         }
948         return false;
949     },
950     do_menu_click: function($clicked_menu, manual) {
951         var $sub_menu, $main_menu,
952             active = $clicked_menu.is('.active'),
953             sub_menu_visible = false;
954
955         if (this.$secondary_menu.has($clicked_menu).length) {
956             $sub_menu = $clicked_menu.parents('.oe_secondary_menu');
957             $main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
958         } else {
959             $sub_menu = this.$secondary_menu.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
960             $main_menu = $clicked_menu;
961         }
962
963         sub_menu_visible = $sub_menu.is(':visible');
964         this.$secondary_menu.find('.oe_secondary_menu').hide();
965
966         $('.active', this.$element.add(this.$secondary_menu)).removeClass('active');
967         $main_menu.add($clicked_menu).add($sub_menu).addClass('active');
968
969         if (!(this.folded && manual)) {
970             this.do_show_secondary($sub_menu, $main_menu);
971         } else {
972             this.do_show_secondary();
973         }
974
975         if ($main_menu != $clicked_menu) {
976             if ($clicked_menu.is('.submenu')) {
977                 $sub_menu.find('.submenu.opened').each(function() {
978                     if (!$(this).next().has($clicked_menu).length && !$(this).is($clicked_menu)) {
979                         $(this).removeClass('opened').next().hide();
980                     }
981                 });
982                 $clicked_menu.toggleClass('opened').next().toggle();
983             } else if ($clicked_menu.is('.leaf')) {
984                 $sub_menu.toggle(!this.folded);
985                 return true;
986             }
987         } else if (this.folded) {
988             if (active && sub_menu_visible) {
989                 $sub_menu.hide();
990                 return true;
991             }
992             return manual;
993         } else {
994             return true;
995         }
996         return false;
997     },
998     do_hide_secondary: function() {
999         this.$secondary_menu.hide();
1000     },
1001     do_show_secondary: function($sub_menu, $main_menu) {
1002         var self = this;
1003         this.$secondary_menu.show();
1004         if (!arguments.length) {
1005             return;
1006         }
1007         if (this.folded) {
1008             var css = $main_menu.position(),
1009                 fold_width = this.$secondary_menu.width() + 2,
1010                 window_width = $(window).width();
1011             css.top += 33;
1012             css.left -= Math.round(($sub_menu.width() - $main_menu.width()) / 2);
1013             css.left = css.left < fold_width ? fold_width : css.left;
1014             if ((css.left + $sub_menu.width()) > window_width) {
1015                 delete(css.left);
1016                 css.right = 1;
1017             }
1018             $sub_menu.css(css);
1019             $sub_menu.mouseenter(function() {
1020                 clearTimeout($sub_menu.data('timeoutId'));
1021                 $sub_menu.data('timeoutId', null);
1022                 return false;
1023             }).mouseleave(function(evt) {
1024                 var timeoutId = setTimeout(function() {
1025                     if (self.folded && $sub_menu.data('timeoutId')) {
1026                         $sub_menu.hide().unbind('mouseenter').unbind('mouseleave');
1027                     }
1028                 }, self.float_timeout);
1029                 $sub_menu.data('timeoutId', timeoutId);
1030                 return false;
1031             });
1032         }
1033         $sub_menu.show();
1034     },
1035     on_menu_action_loaded: function(data) {
1036         var self = this;
1037         if (data.action.length) {
1038             var action = data.action[0][2];
1039             action.from_menu = true;
1040             self.on_action(action);
1041         } else {
1042             self.on_action({type: 'null_action'});
1043         }
1044     },
1045     on_action: function(action) {
1046     }
1047 });
1048
1049 openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClient */{
1050     /**
1051      * @constructs openerp.web.WebClient
1052      * @extends openerp.web.Widget
1053      *
1054      * @param element_id
1055      */
1056     init: function(parent) {
1057         var self = this;
1058         this._super(parent);
1059         openerp.webclient = this;
1060
1061         this._current_state = null;
1062     },
1063     render_element: function() {
1064         this.$element = $('<body/>');
1065         this.$element.attr("id", "oe");
1066         this.$element.addClass("openerp");
1067     },
1068     start: function() {
1069         var self = this;
1070         if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
1071             this.$element.addClass("kitten-mode-activated");
1072             this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) {
1073                 self.$element.toggleClass('clark-gable');
1074             });
1075         }
1076         this.session.bind().then(function() {
1077             if (!self.session.session_is_valid()) {
1078                 self.show_login();
1079             }
1080         });
1081         this.session.on_session_valid.add(function() {
1082             self.show_application();
1083             
1084             self.header.do_update();
1085             self.menu.do_reload();
1086             if(self.action_manager)
1087                 self.action_manager.stop();
1088             self.action_manager = new openerp.web.ActionManager(self);
1089             self.action_manager.appendTo($("#oe_app"));
1090             self.bind_hashchange();
1091             if (!self.session.openerp_entreprise) {
1092                 self.$element.find('.oe_footer_powered').append('<span> - <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">Unsupported/Community Version</a></span>');
1093                 $('title').html('OpenERP - Unsupported/Community Version');
1094             }
1095         });
1096     },
1097     show_login: function() {
1098         var self = this;
1099         this.destroy_content();
1100         this.show_common();
1101         self.login = new openerp.web.Login(self);
1102         self.login.appendTo(self.$element);
1103     },
1104     show_application: function() {
1105         var self = this;
1106         this.destroy_content();
1107         this.show_common();
1108         self.$table = $(QWeb.render("Interface", {}));
1109         self.$element.append(self.$table);
1110         self.header = new openerp.web.Header(self);
1111         self.header.on_logout.add(self.on_logout);
1112         self.header.on_action.add(self.on_menu_action);
1113         self.header.appendTo($("#oe_header"));
1114         self.menu = new openerp.web.Menu(self, "oe_menu", "oe_secondary_menu");
1115         self.menu.on_action.add(self.on_menu_action);
1116         self.menu.start();
1117     },
1118     show_common: function() {
1119         var self = this;
1120         self.crashmanager =  new openerp.web.CrashManager();
1121         self.notification = new openerp.web.Notification(self);
1122         self.notification.appendTo(self.$element);
1123         self.loading = new openerp.web.Loading(self);
1124         self.loading.appendTo(self.$element);
1125     },
1126     destroy_content: function() {
1127         _.each(_.clone(this.widget_children), function(el) {
1128             el.stop();
1129         });
1130         this.$element.children().remove();
1131     },
1132     do_reload: function() {
1133         return this.session.session_init().pipe(_.bind(function() {this.menu.do_reload();}, this));
1134     },
1135     do_notify: function() {
1136         var n = this.notification;
1137         n.notify.apply(n, arguments);
1138     },
1139     do_warn: function() {
1140         var n = this.notification;
1141         n.warn.apply(n, arguments);
1142     },
1143     on_logout: function() {
1144         this.session.session_logout();
1145         $(window).unbind('hashchange', this.on_hashchange);
1146         this.do_push_state({});
1147         //would be cool to be able to do this, but I think it will make addons do strange things
1148         //this.show_login();
1149         window.location.reload();
1150     },
1151     bind_hashchange: function() {
1152         $(window).bind('hashchange', this.on_hashchange);
1153
1154         var state = $.bbq.getState(true);
1155         if (! _.isEmpty(state)) {
1156             $(window).trigger('hashchange');
1157         } else {
1158             this.action_manager.do_action({type: 'ir.actions.client', tag: 'default_home'});
1159         }
1160     },
1161     on_hashchange: function(event) {
1162         var state = event.getState(true);
1163         if (!_.isEqual(this._current_state, state)) {
1164             this.action_manager.do_load_state(state);
1165         }
1166         this._current_state = state;
1167     },
1168     do_push_state: function(state) {
1169         var url = '#' + $.param(state);
1170         this._current_state = _.clone(state);
1171         $.bbq.pushState(url);
1172     },
1173     on_menu_action: function(action) {
1174         this.action_manager.do_action(action);
1175     },
1176     do_action: function(action) {
1177         var self = this;
1178         // TODO replace by client action menuclick 
1179         if(action.menu_id) {
1180             this.do_reload().then(function () {
1181                 self.menu.on_menu_click(null, action.menu_id);
1182             });
1183         }
1184     },
1185 });
1186
1187 openerp.web.EmbeddedClient = openerp.web.Widget.extend({
1188     template: 'EmptyComponent',
1189     init: function(action_id, options) {
1190         this._super();
1191         // TODO take the xmlid of a action instead of its id 
1192         this.action_id = action_id;
1193         this.options = options || {};
1194         this.am = new openerp.web.ActionManager(this);
1195     },
1196
1197     start: function() {
1198         var self = this;
1199         this.am.appendTo(this.$element.addClass('openerp'));
1200         return this.rpc("/web/action/load", { action_id: this.action_id }, function(result) {
1201             var action = result.result;
1202             action.flags = _.extend({
1203                 //views_switcher : false,
1204                 search_view : false,
1205                 action_buttons : false,
1206                 sidebar : false
1207                 //pager : false
1208             }, self.options, action.flags || {});
1209
1210             self.am.do_action(action);
1211         });
1212     },
1213
1214 });
1215
1216 openerp.web.embed = function (origin, dbname, login, key, action, options) {
1217     $('head').append($('<link>', {
1218         'rel': 'stylesheet',
1219         'type': 'text/css',
1220         'href': origin +'/web/webclient/css'
1221     }));
1222     var currentScript = document.currentScript;
1223     if (!currentScript) {
1224         var sc = document.getElementsByTagName('script');
1225         currentScript = sc[sc.length-1];
1226     }
1227     openerp.connection.bind(origin).then(function () {
1228         openerp.connection.session_authenticate(dbname, login, key, true).then(function () {
1229             var client = new openerp.web.EmbeddedClient(action, options);
1230             client.insertAfter(currentScript);
1231         });
1232     });
1233
1234 }
1235
1236 };
1237
1238 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: